Best Practices for Implementing Auth System in Chrome Extension Connected to OpenSaaS

Hello, everyone! I'm currently developing a SaaS product and have created a Chrome extension. I'm at the stage where I need to implement an authentication system that connects the Chrome extension with our SaaS backend, which I've referred to as OpenSaaS for this example. The primary goal is to securely authenticate users through the Chrome extension, ensuring that only authorized users can access and use the extension's features. Here's what I have in mind for the authentication flow: - Users click on the extension icon and are prompted to log in through a popup if they aren't already authenticated. - Upon logging in, the credentials are sent to OpenSaaS's authentication API. - The API returns a token upon successful authentication, which the extension then stores securely. - This token is used for subsequent API calls to authenticate the user. I'm looking for advice on the following: - Secure Token Storage: What are the best practices for securely storing and managing the authentication token within a Chrome extension? - Authentication Flow: Is there a recommended pattern or best practice for implementing the authentication flow in a Chrome extension, especially concerning SaaS products? - API Communication: Any tips on securing the communication between the Chrome extension and the SaaS backend? Additionally, if there are any security considerations or common pitfalls I should be aware of, I'd greatly appreciate your insights. Thank you in advance for your help and guidance!
44 Replies
Filip
Filip8mo ago
Hi @Álvaro P., I've never worked with Chrome extensions. Did some quick googling and found a couple of links I could point you to, but you've probably found the same links yourself 😄 I'll tag @miho in hope he knows something about extensions, because I really am a blank slate in this regard. What I can say is that, for the "Any tips on securing the communication between the Chrome extension and the SaaS backend?" part, as long as you're using HTTPS, there's nothing extra you have to do - the communication is as secure as it reasonably needs to be.
miho
miho8mo ago
Hm, off the top of my head, use custom API endpoints and have a way to create JWT tokens (We used oslo for this internally). Store the JWT tokens in local storage. What you put in the JWT? Probably just some user ID would be enough. On each request to your custom API, send that JWT in the headers. Check it on each request. We did really work on this "third-party app is authenticated" scenario. Hmmm @martinsos is this similar to mobile apps using Wasp?
Álvaro P.
Álvaro P.OP8mo ago
Hi guys! Thanks for the answers. After searching a lot, as @miho says that is the key. The key is to generate a JWT key when the user logs in. I'm having trouble with the /api/generate-jwt endpoint in my Wasp app. Although I've defined the endpoint in my main.wasp file and implemented the generateJwt function in TypeScript, when I test the endpoint with curl, it returns a 404 Not Found error. I've made sure to restart the Wasp server after making changes. Could someone help me understand why the endpoint might not be found, even though it's been defined? Here's how I've set up the API and function: // main.wasp api generateJwt { fn: import { generateJwt } from "@src/server/api/generateJwt.js", entities: [User], httpRoute: (POST, "/api/generate-jwt") } The generateJwt.ts script is responsible for handling user authentication and JWT (JSON Web Token) creation in a Wasp application. It includes a mock authentication function that, for demonstration purposes, approves any login attempt and assigns a mock user ID. Upon successful authentication, it generates a JWT containing the user's username and user ID, signs it with a secret key retrieved from the environment, and sends the token back to the client. // generateJwt.ts import * as jwt from 'jsonwebtoken'; import { Request, Response } from 'express'; // Adjusted authenticateUser function to also return user ID upon successful authentication async function authenticateUser(username: string, password: string): Promise<{ isAuthenticated: boolean; userId?: string }> { // TODO: Implement actual user authentication logic here // This should fetch the user's ID from the database based on the provided username and password // For demonstration, assuming authentication is successful and returning a mock user ID return { isAuthenticated: true, userId: 'user123' }; } export async function generateJwt(req: Request, res: Response): Promise<void> { const { username, password } = req.body; try { const authResult = await authenticateUser(username, password); if (!authResult.isAuthenticated) { res.status(401).json({ error: 'Invalid credentials' }); return; } // User payload for JWT const userPayload = { username, userId: authResult.userId }; // Retrieve the secret key from environment variables const secretKey = process.env.JWT_SECRET_KEY; if (!secretKey) { throw new Error('JWT secret key is missing'); } // Token options const options = { expiresIn: '1h' }; // Token expires in 1 hour // Generate the JWT const token = jwt.sign(userPayload, secretKey, options); // Send the JWT back to the client res.json({ token }); } catch (error) { console.error('Error generating JWT token:', error); res.status(500).json({ error: 'Internal server error' }); } }
MEE6
MEE68mo ago
Wohooo @Álvaro P., you just became a Waspeteer level 1!
Álvaro P.
Álvaro P.OP8mo ago
curl -v -X POST http://localhost:3000/api/generate-jwt \ -H "Content-Type: application/json" \ -d '{"username":"testuser","password":"verysecurepassword"}' Note: Unnecessary use of -X or --request, POST is already inferred. * Trying [::1]:3000... * connect to ::1 port 3000 failed: Connection refused * Trying 127.0.0.1:3000... * Connected to localhost (127.0.0.1) port 3000
POST /api/generate-jwt HTTP/1.1 Host: localhost:3000 User-Agent: curl/8.4.0 Accept: / Content-Type: application/json Content-Length: 55
< HTTP/1.1 404 Not Found < Access-Control-Allow-Origin: * < Date: Tue, 02 Apr 2024 05:29:02 GMT < Connection: keep-alive < Keep-Alive: timeout=5 < Content-Length: 0 < * Connection #0 to host localhost left intact
Filip
Filip8mo ago
HI @Álvaro P., I'll get on this ASAP. Btw, here's a protip, you can use code blocks and syntax highlighting in discord as you would on github: This code
`​`​`typescript
// generateJwt.ts
import * as jwt from 'jsonwebtoken';
import { Request, Response } from 'express';

// Adjusted authenticateUser function to also return user ID upon successful authentication
async function authenticateUser(username: string, password: string): Promise<{ isAuthenticated: boolean; userId?: string }> {
// ...
}

// ...
`​`​`
`​`​`typescript
// generateJwt.ts
import * as jwt from 'jsonwebtoken';
import { Request, Response } from 'express';

// Adjusted authenticateUser function to also return user ID upon successful authentication
async function authenticateUser(username: string, password: string): Promise<{ isAuthenticated: boolean; userId?: string }> {
// ...
}

// ...
`​`​`
Renders to this:
// generateJwt.ts
import * as jwt from 'jsonwebtoken';
import { Request, Response } from 'express';

// Adjusted authenticateUser function to also return user ID upon successful authentication
async function authenticateUser(username: string, password: string): Promise<{ isAuthenticated: boolean; userId?: string }> {
// ...
}

// ...
// generateJwt.ts
import * as jwt from 'jsonwebtoken';
import { Request, Response } from 'express';

// Adjusted authenticateUser function to also return user ID upon successful authentication
async function authenticateUser(username: string, password: string): Promise<{ isAuthenticated: boolean; userId?: string }> {
// ...
}

// ...
Good news @Álvaro P.! Since you've done an excellent job describing the problem, this was an easy one to diagnose (and luckily fix) 😄 The server is running on port 3001, not 3000. Wasp spins up two processes: one serving the client files (port 3000), and another one serving the server files (port 3001). Anyway, change the port and everything should work:
curl -v -X POST http://localhost:3001/api/generate-jwt \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"verysecurepassword"}'
curl -v -X POST http://localhost:3001/api/generate-jwt \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"verysecurepassword"}'
martinsos
martinsos8mo ago
@miho : yeah, this is connected to the effort of making it easier to use auth in "third-party" apps, including mobile. Ideally, for this chrome extension, a Wasp dev would just use the exact same logic as we do for the Wasp app, on the client side. We don't, however, currently have a super easy way for them to get that logic available -> what we would probably want to do at some point is generate a small JS/TS package that does only auth, that they can import in other projects. And not only JS/TS, but also C#, Java, ... (to support mobile). It would be interesting to explore what this package could look like, and how would they use in their third party app during development, how it would fit into the dev pipeline of it, be it chrome extension or a mobile app or something else. Since we don't have that now, what is instead needed is replicating the logic we have themselves, completely or to some degree. I do wonder how hard is that at the moment? @miho you will know the best, I would love to understand it better, how much work do they actually need to do at the moment to replicate, and how much is done by Wasp's server? p.s. we have an issue on GH for enabling auth for external clients, we should add this info there once we are done discussing, it will be helpful.
Connor
Connor4mo ago
Hello, has any progress been made on this front for using auth with "thrid-party" apps? We are looking at chrome extensions Also is there a way to authenticate the user through an API call? We are looking to authenticate the user inside a custom API call to generate a jwt but don't know which function to call to authenticate the user once they pass in both username and password from the external api call
martinsos
martinsos4mo ago
@miho
miho
miho4mo ago
know which function to call to authenticate the user once they pass in both username and password from the external api call
There isn't really a function 😄 since you are recreating a part of the Wasp auth, you need to do what we do under the hood: find the user in the DB and check if the password is OK. This is how you can do it 🙂 let's say you have this custom endpoint set up:
api customLogin {
httpRoute: (POST, "/api/custom-login"),
fn: import { customLogin } from "@src/api",
entities: []
}

apiNamespace customApiNamespace {
middlewareConfigFn: import { getMiddleware } from "@src/api",
path: "/api"
}
api customLogin {
httpRoute: (POST, "/api/custom-login"),
fn: import { customLogin } from "@src/api",
entities: []
}

apiNamespace customApiNamespace {
middlewareConfigFn: import { getMiddleware } from "@src/api",
path: "/api"
}
miho
miho4mo ago
You can then check the login credentials (let's say I'm using username & password auth) like this: https://gist.github.com/infomiho/baf8330ce3733c64906173b1533f2f45#file-src-api-ts
Gist
Custom login API endpoint
Custom login API endpoint. GitHub Gist: instantly share code, notes, and snippets.
miho
miho4mo ago
I was able to get the session ID with the API endpoint like this:
curl -X POST http://localhost:3001/api/custom-login -d 'username=miho' -d 'password=12345678'
{"sessionId":"5vjadgdux64jrkdsb32bvvndxic4nupfiy7tqaap"}
curl -X POST http://localhost:3001/api/custom-login -d 'username=miho' -d 'password=12345678'
{"sessionId":"5vjadgdux64jrkdsb32bvvndxic4nupfiy7tqaap"}
So you just need to send that session ID in the headers like so:
request.headers['Authorization'] = `Bearer ${sessionId}`
request.headers['Authorization'] = `Bearer ${sessionId}`
Gyasi
Gyasi4mo ago
Hi, when i try to create the custom login and run test the APIU endpoint, i get "message": "Invalid username or password" but i am certain they are correct. I have even added statements and it comes back that the user is not found. is there something i am doing wrong?
miho
miho4mo ago
Could you share your core in a gist?
miho
miho4mo ago
What do you get in the server logs? That's it's an invalid pw or the user doesn't exist?
Gyasi
Gyasi4mo ago
user does not exist
miho
miho4mo ago
Did you register for an account with that username previouly? It needs to exist in the DB
Gyasi
Gyasi4mo ago
yes i have registered with email and with google and nothing
miho
miho4mo ago
Did you confirm the email after you registered? What do you see in the DB when you run wasp db studio?
Gyasi
Gyasi4mo ago
Yes I confirmed everything and the users are there in the dB. I have even made one of the users a pro user with stripe
miho
miho4mo ago
I'd advise you to compare this line to what you have in the DB: https://gist.github.com/gyasi87/3a171e7a2631dc9965f9bc70952e65e3#file-api-ts-L35 Hint: if you are using the email provider, it should say "email" instead of "username" there 😄
Gist
api.ts
GitHub Gist: instantly share code, notes, and snippets.
Gyasi
Gyasi4mo ago
For some reason with email and google the username is the email in the db. I tried the code at the top to generate the jwt token and that worked with the user email I signed up with email.
MEE6
MEE64mo ago
Wohooo @Gyasi, you just became a Waspeteer level 1!
Gyasi
Gyasi4mo ago
@miho i changed username to email and it worked with the email provider as you said. thank you. is there a way to get it to work with google provider?
miho
miho4mo ago
There isn't a simple way to do it, you'd have to recreate the Google OAuth provider with custom API endpoints e.g. https://gist.github.com/infomiho/3c63de7d53aba59d6293bcb59501a029 We are working on this to make it easier to use Wasp with external apps https://github.com/wasp-lang/wasp/issues/1973
Gist
Implementing custom OAuth provider with Wasp 0.14.0+ (Spotify in th...
Implementing custom OAuth provider with Wasp 0.14.0+ (Spotify in this case) - main.wasp
GitHub
Make Wasp Auth usable in external apps / services · Issue #1973 · w...
We have users that are building Wasp app, but they also have some other apps that they want to use the same auth system (Wasp Auth). For example, we had a user building a Unity game, and wanted to ...
Gyasi
Gyasi4mo ago
ok great thank you for all your help
fred-123
fred-1234mo ago
@miho do you guys have a rough timeline when this would be implemented?
WWWillems
WWWillems4mo ago
Also interested in implementing this feature, so would also love a rough timeline 🙂
fred-123
fred-1234mo ago
No description
martinsos
martinsos4mo ago
Hah, it is one of the very next things we will be working on regarding Auth, and it is on the "let's try to do it if we will manage" list for this quarter (Q3). right after "these features we must finish this quarter". Meaning it might happen this quarter, and if not, will very likely become top priority for the next quarter. This quarter is a bit tricky because a lot of us are going for vacations this or next month since we have been working quite hard so far this year, so this quarter will be a bit more modest. But we do understand the importance of this feature and it is on top of our minds! @miho the crowd is going wild
WWWillems
WWWillems4mo ago
Awesome, thanks for the update 🙏
fred-123
fred-1234mo ago
No description
miho
miho4mo ago
Yeah, as @martinsos said, it's pretty high on our priority list. Getting the email and username methods to work - should be straightforward. But getting the Google and other OAuth methods to work could prove tricky to a degree because there are redirects involved 😄 it'll probably require some sort of a setup like when you authenticate a CLI via a browser - two separate contexts that need to communicate securely. But anyho, it will take time and energy to get this down right 😄
Sven
Sven3mo ago
@kapa.ai I want on button click from my SaaS a token generated that i can send to my chrome extension using sendMessage. Then when from the chrome extensions requests are mdae to my SaaS i can authorize this token based on subscriptionplan and status. How can i do this?
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Based on the information provided in the knowledge sources, you can follow these steps to implement your desired functionality: 1. Generate a JWT token: When a user logs in from your SaaS, generate a JWT token. This token can be generated in your Wasp application using a custom API endpoint. The token can include user-specific information such as user ID and subscription plan. Here's an example of how you can set up the API and function:
// main.wasp
api generateJwt {
fn: import { generateJwt } from "@src/server/api/generateJwt.js",
entities: [User],
httpRoute: (POST, "/api/generate-jwt")
}

// generateJwt.ts
import * as jwt from 'jsonwebtoken';
import { Request, Response } from 'express';

async function authenticateUser(username: string, password: string): Promise<{ isAuthenticated: boolean; userId?: string }> {
// Implement actual user authentication logic here
// This should fetch the user's ID from the database based on the provided username and password
// For demonstration, assuming authentication is successful and returning a mock user ID
return { isAuthenticated: true, userId: 'user123' };
}

export async function generateJwt(req: Request, res: Response): Promise<void> {
const { username, password } = req.body;
const authResult = await authenticateUser(username, password);
if (!authResult.isAuthenticated) {
res.status(401).json({ error: 'Invalid credentials' });
return;
}

// User payload for JWT
const userPayload = { username, userId: authResult.userId };

// Retrieve the secret key from environment variables
const secretKey = process.env.JWT_SECRET_KEY;
if (!secretKey) {
throw new Error('JWT secret key is missing');
}

// Token options
const options = { expiresIn: '1h' }; // Token expires in 1 hour
// Generate the JWT
const token = jwt.sign(userPayload, secretKey, options);

// Send the JWT back to the client
res.json({ token });
}
// main.wasp
api generateJwt {
fn: import { generateJwt } from "@src/server/api/generateJwt.js",
entities: [User],
httpRoute: (POST, "/api/generate-jwt")
}

// generateJwt.ts
import * as jwt from 'jsonwebtoken';
import { Request, Response } from 'express';

async function authenticateUser(username: string, password: string): Promise<{ isAuthenticated: boolean; userId?: string }> {
// Implement actual user authentication logic here
// This should fetch the user's ID from the database based on the provided username and password
// For demonstration, assuming authentication is successful and returning a mock user ID
return { isAuthenticated: true, userId: 'user123' };
}

export async function generateJwt(req: Request, res: Response): Promise<void> {
const { username, password } = req.body;
const authResult = await authenticateUser(username, password);
if (!authResult.isAuthenticated) {
res.status(401).json({ error: 'Invalid credentials' });
return;
}

// User payload for JWT
const userPayload = { username, userId: authResult.userId };

// Retrieve the secret key from environment variables
const secretKey = process.env.JWT_SECRET_KEY;
if (!secretKey) {
throw new Error('JWT secret key is missing');
}

// Token options
const options = { expiresIn: '1h' }; // Token expires in 1 hour
// Generate the JWT
const token = jwt.sign(userPayload, secretKey, options);

// Send the JWT back to the client
res.json({ token });
}
(Source: Discord support thread: Best Practices for Implementing Auth System in Chrome Extension Connected to OpenSaaS (https://discord.com/channels/686873244791210014/1223328118278983680)) 2. Send the token to the Chrome extension: After generating the token, you can send it to your Chrome extension using chrome.runtime.sendMessage. 3. Store the token in the Chrome extension: Once the token is received in the Chrome extension, you can store it securely for subsequent use. 4. Authorize requests from the Chrome extension: When the Chrome extension makes requests to your SaaS, it should include the stored token in the request headers. On the server side, you can then validate this token and authorize the request based on the user's subscription plan and status. Please note that this is a high-level overview and the actual implementation may vary based on your specific requirements and the technologies you are using. Also, remember to always use HTTPS for secure communication between the Chrome extension and your SaaS backend. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @Sven, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
Sven
Sven3mo ago
@kapa.ai How could i do the authentication on the SaaS side and then send the token to the chrome extensions. Then when a request is made frome the ext. to the SaaS it should check the subcriptoin status before it 200OK.
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- To implement the authentication flow you described, you can follow these steps: 1. User Authentication: When a user logs in from your SaaS, you need to authenticate the user. This can be done by sending the user's credentials to your authentication API and verifying them against your user database. 2. Generate JWT Token: Upon successful authentication, generate a JWT token. This token can include user-specific information such as user ID and subscription status. You can use the jsonwebtoken library to generate the token as shown in the code snippet from the knowledge source. 3. Send Token to Chrome Extension: After generating the token, you can send it to your Chrome extension. This can be done using a method suitable for your application, such as through an API response or via direct communication between your SaaS and the extension. 4. Store Token in Chrome Extension: Once the token is received in the Chrome extension, you can store it securely for subsequent use. Chrome extensions provide various storage APIs that you can use for this purpose. 5. Authorize Requests: When the Chrome extension makes requests to your SaaS, it should include the stored token in the request headers. On the server side, you can then validate this token and authorize the request based on the user's subscription status. Here's a simplified example of how you can generate a JWT token in your Wasp application:
// generateJwt.ts
import * as jwt from 'jsonwebtoken';
import { Request, Response } from 'express';
async function authenticateUser(username: string, password: string): Promise<{ isAuthenticated: boolean; userId?: string }> {
// Implement actual user authentication logic here
// This should fetch the user's ID from the database based on the provided username and password
// For demonstration, assuming authentication is successful and returning a mock user ID
return { isAuthenticated: true, userId: 'user123' };
}

export async function generateJwt(req: Request, res: Response): Promise<void> {
const { username, password } = req.body;
const authResult = await authenticateUser(username, password);
if (!authResult.isAuthenticated) {
res.status(401).json({ error: 'Invalid credentials' });
return;
}

// User payload for JWT
const userPayload = { username, userId: authResult.userId };

// Retrieve the secret key from environment variables
const secretKey = process.env.JWT_SECRET_KEY;
if (!secretKey) {
throw new Error('JWT secret key is missing');
}

// Token options
const options = { expiresIn: '1h' }; // Token expires in 1 hour

// Generate the JWT
const token = jwt.sign(userPayload, secretKey, options);

// Send the JWT back to the client
res.json({ token });
}
// generateJwt.ts
import * as jwt from 'jsonwebtoken';
import { Request, Response } from 'express';
async function authenticateUser(username: string, password: string): Promise<{ isAuthenticated: boolean; userId?: string }> {
// Implement actual user authentication logic here
// This should fetch the user's ID from the database based on the provided username and password
// For demonstration, assuming authentication is successful and returning a mock user ID
return { isAuthenticated: true, userId: 'user123' };
}

export async function generateJwt(req: Request, res: Response): Promise<void> {
const { username, password } = req.body;
const authResult = await authenticateUser(username, password);
if (!authResult.isAuthenticated) {
res.status(401).json({ error: 'Invalid credentials' });
return;
}

// User payload for JWT
const userPayload = { username, userId: authResult.userId };

// Retrieve the secret key from environment variables
const secretKey = process.env.JWT_SECRET_KEY;
if (!secretKey) {
throw new Error('JWT secret key is missing');
}

// Token options
const options = { expiresIn: '1h' }; // Token expires in 1 hour

// Generate the JWT
const token = jwt.sign(userPayload, secretKey, options);

// Send the JWT back to the client
res.json({ token });
}
(Source: Discord support thread: Best Practices for Implementing Auth System in Chrome Extension Connected to OpenSaaS (https://discord.com/channels/686873244791210014/1223328118278983680)) Please note that this is a high-level overview and the actual implementation may vary based on your specific requirements and the technologies you are using. Also, remember to always use HTTPS for secure communication between the Chrome extension and your SaaS backend. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @Sven, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
Sven
Sven3mo ago
@kapa.ai why would username and password be nessecary? I want it to maybe check context.user or something
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- In Wasp, the context.user object is used to access the currently logged-in user's information on the server. When authentication is enabled, all queries and actions have access to the user object through the context argument. context.user contains all User entity's fields and the auth identities connected to the user. The hashedPassword field is stripped out from the identities for security reasons. Here's an example of how you can use context.user in your actions:
// src/actions.js
import { HttpError } from 'wasp/server'

export const createTask = async (task, context) => {
if (!context.user) {
throw new HttpError(403)
}

const Task = context.entities.Task
return Task.create({
data: {
description: task.description,
user: {
connect: { id: context.user.id },
},
},
})
}
// src/actions.js
import { HttpError } from 'wasp/server'

export const createTask = async (task, context) => {
if (!context.user) {
throw new HttpError(403)
}

const Task = context.entities.Task
return Task.create({
data: {
description: task.description,
user: {
connect: { id: context.user.id },
},
},
})
}
(Source: Wasp Docs: Using the context.user object (https://wasp-lang.dev/docs/auth/overview#using-the-contextuser-object)) In this example, context.user is checked to ensure that a user is logged in before creating a task. If context.user is undefined, it means that the user is not logged in, and an HTTP 403 error is thrown. The username and password are not necessary in this context. They are typically used for user authentication during the login process. Once a user is authenticated, a token is generated and sent to the client. This token is then included in subsequent requests to the server, which allows the server to identify the user and populate context.user. If you want to check the subscription status of the user, you can add a subscriptionStatus field to your User entity and then access it via context.user.subscriptionStatus. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @Sven, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
Sven
Sven3mo ago
@kapa.ai Error connecting to Chrome extension: TypeError: Cannot read properties of undefined (reading 'sendMessage') at connectChromeExt (ChromeExtensiePage.tsx:32:22)
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The error message you're seeing typically occurs when you're trying to access a property or method on an object that is undefined. In this case, it seems like the object that's supposed to contain the sendMessage method is undefined. Given the context of your question, it's likely that the object in question is chrome.runtime. This object is part of the Chrome extension APIs and should be defined when your code is running as part of a Chrome extension. If it's undefined, it could mean one of the following: 1. Your code is not running as part of a Chrome extension. The Chrome extension APIs are only available to scripts that are running as part of an extension. They are not available to scripts running on regular web pages. 2. The chrome object is not available at the time your code is running. This could happen if your code is executing before the extension APIs have been fully loaded. To debug this issue, you can add some logging to your code to check the value of chrome and chrome.runtime:
console.log('chrome:', chrome);
console.log('chrome.runtime:', chrome.runtime);
console.log('chrome:', chrome);
console.log('chrome.runtime:', chrome.runtime);
If chrome or chrome.runtime is undefined, you'll need to figure out why that's the case. If your code is supposed to be running as part of a Chrome extension, you might need to check your extension's manifest file to make sure it's correctly configured. If chrome and chrome.runtime are defined but chrome.runtime.sendMessage is undefined, it could mean that the sendMessage API is not available. This could happen if the API is not supported by the version of Chrome you're using, or if the API is not included in the "permissions" field of your extension's manifest file. Please note that without the actual code or more specific details about your extension's setup, it's hard to provide a more specific solution. The above is a general guide based on the error message and the typical causes of such an error. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @Sven, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
Want results from more Discord servers?
Add your server