Skip to content

Commit c90d835

Browse files
royangertmilewski
andauthored
feat(chrome-extension): Create js only create clerk client for extensions (#7935)
Co-authored-by: Tom Milewski <me@tm.codes>
1 parent cd1973e commit c90d835

26 files changed

Lines changed: 763 additions & 79 deletions

File tree

.changeset/wild-pianos-matter.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@clerk/chrome-extension': minor
3+
---
4+
5+
Added new `createClerkClient()` export from @clerk/chrome-extension/client
6+
7+
```ts
8+
import { createClerkClient } from '@clerk/chrome-extension/client';
9+
10+
const publishableKey = process.env.CLERK_PUBLISHABLE_KEY;
11+
// Use createClerkClient in a popup or side panel
12+
const clerk = createClerkClient({ publishableKey });
13+
14+
// Use createClerkClient in a background service worker
15+
const clerk = await createClerkClient({ publishableKey: 'pk_...', background: true });
16+
```
17+
18+
`createClerkClient()` from @clerk/chrome-extension/background is deprecated.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ out-tsc
77
out
88
**/dist/*
99
**/build/*
10+
!playground/browser-extension-js/build/manifest.json
11+
!playground/browser-extension-js/build/popup.html
12+
!playground/browser-extension-js/build/popup.css
1013
packages/*/dist/**
1114
**/.pnpm-store/**
1215

@@ -62,6 +65,7 @@ lerna-debug.log
6265
.dev.vars
6366
.env.local
6467
playground/*/build
68+
!playground/browser-extension-js/build
6569
playground/*/public/build
6670
playground/*/.cache
6771
playground/custom

packages/chrome-extension/README.md

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -41,41 +41,44 @@ Please see the latest extension [authentication support matrix](https://clerk.co
4141

4242
### Usage
4343

44-
1. **Installation:** `npm install @clerk/chrome-extension`
45-
2. **Set a consistent extension key**: A browser extension can be identified by its unique key, in a similar way to how a website can be identified by its domain. You will need to explicitly configure your extension's key or it will change often. If the key changes, it can cause the extension to fail. See the [Configure a Consistent Key](https://clerk.com/docs/references/chrome-extension/configure-consistent-crx-id?utm_source=github&utm_medium=clerk_chrome_extension) guide for more information.
46-
3. **Update Clerk Settings**: Once you've set up a consistent extension key, you'll need to configure your Clerk settings to allow the extension to communicate with your Clerk API.
47-
You can do this by adding the extension key to the list of allowed origins in your Clerk settings. Setting the `allowed_origins` is **required** for both **Development** and **Production** instances.
48-
49-
```bash
50-
curl -X PATCH https://api.clerk.com/v1/instance \
51-
-H "Content-type: application/json" \
52-
-H "Authorization: Bearer <CLERK_SECRET_KEY>" \
53-
-d '{"allowed_origins": ["chrome-extension://<YOUR_EXTENSION_KEY>"]}'
54-
```
55-
56-
4. **Set Environment Variables:** Retrieve the **Publishable key** from your [Clerk dashboard](https://dashboard.clerk.com/last-active?path=api-keys&utm_source=github&utm_medium=clerk_chrome_extension) and set it as an environment variable.
57-
58-
```sh
59-
# Vite
60-
VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxx
61-
```
62-
63-
```sh
64-
# Plasmo
65-
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx
66-
```
67-
68-
5. **Update the extension manifest:** You'll need to update your extension manifest permissions to support Clerk.
69-
1. [**Base configuration**:](/packages/chrome-extension/docs/manifest.md#base-configuration) Use this if you plan to only use Clerk in the context of the extension.
70-
2. [**Session sync configuration**:](/packages/chrome-extension/docs/manifest.md#sync-host-configuration) Use this if you plan to share authentication with a website in the same browser.
71-
6. **Add Clerk to your app:** Though not required, we generally suggest using Plasmo for browser extension development. This will enforce common standards across your extension as well as allow for easier integration with other browsers in the future.
72-
1. [**Via `ClerkProvider`:**](/packages/chrome-extension/docs/clerk-provider.md) This is the general approach to all extensions. From here you'll be able to support extension-only authentication as well as sharing authentication with a website in the same browser.
73-
2. [**Via service workers**:](/packages/chrome-extension/docs/service-workers.md) If you also require the use of background service workers, this will allow you to access the Clerk client from the extension context.
44+
1. **Installation:** `npm install @clerk/chrome-extension`
45+
46+
2. **Set a consistent extension key**: A browser extension can be identified by its unique key, in a similar way to how a website can be identified by its domain. You will need to explicitly configure your extension's key or it will change often. If the key changes, it can cause the extension to fail. See the [Configure a Consistent Key](https://clerk.com/docs/references/chrome-extension/configure-consistent-crx-id?utm_source=github&utm_medium=clerk_chrome_extension) guide for more information.
47+
48+
3. **Update Clerk Settings**: Once you've set up a consistent extension key, you'll need to configure your Clerk settings to allow the extension to communicate with your Clerk API.
49+
You can do this by adding the extension key to the list of allowed origins in your Clerk settings. Setting the `allowed_origins` is **required** for both **Development** and **Production** instances.
50+
51+
```bash
52+
curl -X PATCH https://api.clerk.com/v1/instance \
53+
-H "Content-type: application/json" \
54+
-H "Authorization: Bearer <CLERK_SECRET_KEY>" \
55+
-d '{"allowed_origins": ["chrome-extension://<YOUR_EXTENSION_KEY>"]}'
56+
```
57+
58+
4. **Set Environment Variables:** Retrieve the **Publishable key** from your [Clerk dashboard](https://dashboard.clerk.com/last-active?path=api-keys&utm_source=github&utm_medium=clerk_chrome_extension) and set it as an environment variable.
59+
60+
```sh
61+
# Vite
62+
VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxx
63+
```
64+
65+
```sh
66+
# Plasmo
67+
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx
68+
```
69+
70+
5. **Update the extension manifest:** You'll need to update your extension manifest permissions to support Clerk.
71+
1. [**Base configuration**:](/packages/chrome-extension/docs/manifest.md#base-configuration) Use this if you plan to only use Clerk in the context of the extension.
72+
2. [**Session sync configuration**:](/packages/chrome-extension/docs/manifest.md#sync-host-configuration) Use this if you plan to share authentication with a website in the same browser.
73+
74+
6. **Add Clerk to your app:** Though not required, we generally suggest using Plasmo for browser extension development. This will enforce common standards across your extension as well as allow for easier integration with other browsers in the future.
75+
1. [**Via `ClerkProvider`:**](/packages/chrome-extension/docs/clerk-provider.md) This is the general approach to all extensions. From here you'll be able to support extension-only authentication as well as sharing authentication with a website in the same browser.
76+
2. [**Via service workers**:](/packages/chrome-extension/docs/service-workers.md) If you also require the use of background service workers, this will allow you to access the Clerk client from the extension context.
7477

7578
## Example repositories
7679

77-
- [Standalone](https://github.com/clerk/clerk-chrome-extension-starter/tree/main): The extension is using its own authentication
78-
- [WebSSO](https://github.com/clerk/clerk-chrome-extension-starter/tree/webapp_sso): The extensions shares authentication with a website in the same browser
80+
- [Quickstart](https://github.com/clerk/clerk-chrome-extension-quickstart): The extension is using its own authentication
81+
- [SyncHost, Service Workers and `react-router`](https://github.com/clerk/clerk-chrome-extension-demo): The extension shares auth with a website in the same browser, needs to access user information from Clerk in a service worker or needs to use `react-router`
7982

8083
## Support
8184

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"main": "../dist/cjs/client/index.js",
3+
"module": "../dist/esm/client/index.js",
4+
"types": "../dist/types/client/index.d.ts"
5+
}

packages/chrome-extension/package.json

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,48 @@
2222
},
2323
"license": "MIT",
2424
"sideEffects": false,
25+
"exports": {
26+
".": {
27+
"import": {
28+
"types": "./dist/types/index.d.ts",
29+
"default": "./dist/esm/index.js"
30+
},
31+
"require": {
32+
"types": "./dist/types/index.d.ts",
33+
"default": "./dist/cjs/index.js"
34+
}
35+
},
36+
"./client": {
37+
"import": {
38+
"types": "./dist/types/client/index.d.ts",
39+
"default": "./dist/esm/client/index.js"
40+
},
41+
"require": {
42+
"types": "./dist/types/client/index.d.ts",
43+
"default": "./dist/cjs/client/index.js"
44+
}
45+
},
46+
"./background": {
47+
"import": {
48+
"types": "./dist/types/background/index.d.ts",
49+
"default": "./dist/esm/background/index.js"
50+
},
51+
"require": {
52+
"types": "./dist/types/background/index.d.ts",
53+
"default": "./dist/cjs/background/index.js"
54+
}
55+
},
56+
"./types": {
57+
"types": "./dist/types/types/index.d.ts"
58+
},
59+
"./package.json": "./package.json"
60+
},
2561
"main": "./dist/cjs/index.js",
2662
"module": "./dist/esm/index.js",
2763
"types": "./dist/types/index.d.ts",
2864
"files": [
2965
"background",
66+
"client",
3067
"dist",
3168
"internal",
3269
"react",
@@ -42,7 +79,7 @@
4279
"format": "node ../../scripts/format-package.mjs",
4380
"format:check": "node ../../scripts/format-package.mjs --check",
4481
"lint": "eslint src",
45-
"lint:attw": "attw --pack . --profile node16",
82+
"lint:attw": "attw --pack . --profile node16 --ignore-rules unexpected-module-syntax",
4683
"lint:publint": "publint",
4784
"test": "vitest run",
4885
"test:ci": "vitest run --maxWorkers=70%",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`client public exports > should not include a breaking change 1`] = `
4+
[
5+
"createClerkClient",
6+
]
7+
`;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import * as publicExports from '../client';
4+
5+
describe('client public exports', () => {
6+
it('should not include a breaking change', () => {
7+
expect(Object.keys(publicExports).sort()).toMatchSnapshot();
8+
});
9+
});

packages/chrome-extension/src/background/clerk.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,34 @@ import type { Clerk } from '@clerk/clerk-js/no-rhc';
33
import {
44
createClerkClient as _createClerkClient,
55
type CreateClerkClientOptions as _CreateClerkClientOptions,
6-
} from '../internal';
7-
import { SCOPE } from '../types';
6+
} from '../utils/clerk-client';
87

9-
export type CreateClerkClientOptions = Omit<_CreateClerkClientOptions, 'scope'>;
8+
/**
9+
* @deprecated Use `createClerkClient` from `@clerk/chrome-extension/client` with `{ background: true }` instead.
10+
*
11+
* @example
12+
* // Before (deprecated):
13+
* import { createClerkClient } from '@clerk/chrome-extension/background';
14+
* const clerk = await createClerkClient({ publishableKey: 'pk_...' });
15+
*
16+
* // After:
17+
* import { createClerkClient } from '@clerk/chrome-extension/client';
18+
* const clerk = await createClerkClient({ publishableKey: 'pk_...', background: true });
19+
*/
20+
export type CreateClerkClientOptions = Omit<_CreateClerkClientOptions, 'background'>;
1021

11-
export async function createClerkClient(opts: CreateClerkClientOptions): Promise<Clerk> {
12-
const clerk = await _createClerkClient({ ...opts, scope: SCOPE.BACKGROUND });
13-
await clerk.load({ standardBrowser: false });
14-
return clerk;
22+
/**
23+
* @deprecated Use `createClerkClient` from `@clerk/chrome-extension/client` with `{ background: true }` instead.
24+
*
25+
* @example
26+
* // Before (deprecated):
27+
* import { createClerkClient } from '@clerk/chrome-extension/background';
28+
* const clerk = await createClerkClient({ publishableKey: 'pk_...' });
29+
*
30+
* // After:
31+
* import { createClerkClient } from '@clerk/chrome-extension/client';
32+
* const clerk = await createClerkClient({ publishableKey: 'pk_...', background: true });
33+
*/
34+
export function createClerkClient(opts: CreateClerkClientOptions): Promise<Clerk> {
35+
return _createClerkClient({ ...opts, background: true }) as Promise<Clerk>;
1536
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { createClerkClient } from '../utils/clerk-client';
2+
export type { CreateClerkClientOptions } from '../utils/clerk-client';
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
const mockLoad = vi.fn().mockResolvedValue(undefined);
4+
const mockUi = { __brand: 'clerk-ui', ClerkUI: vi.fn() };
5+
6+
vi.mock('@clerk/clerk-js/no-rhc', () => {
7+
const Clerk = vi.fn(() => ({
8+
load: mockLoad,
9+
})) as ReturnType<typeof vi.fn> & { sdkMetadata: Record<string, string> };
10+
Clerk.sdkMetadata = {};
11+
return { Clerk };
12+
});
13+
14+
vi.mock('@clerk/ui', () => ({
15+
ui: mockUi,
16+
}));
17+
18+
import { createClerkClient } from '../clerk-client';
19+
20+
describe('createClerkClient', () => {
21+
beforeEach(() => {
22+
vi.clearAllMocks();
23+
});
24+
25+
describe('non-background (popup)', () => {
26+
it('returns a Clerk instance synchronously', () => {
27+
const clerk = createClerkClient({ publishableKey: 'pk_test_123' });
28+
expect(clerk).toBeDefined();
29+
expect(clerk).not.toBeInstanceOf(Promise);
30+
});
31+
32+
it('wraps load() to inject @clerk/ui', async () => {
33+
const clerk = createClerkClient({ publishableKey: 'pk_test_123' });
34+
const loadOpts = { afterSignOutUrl: '/signed-out' };
35+
36+
await clerk.load(loadOpts);
37+
38+
expect(mockLoad).toHaveBeenCalledOnce();
39+
expect(mockLoad).toHaveBeenCalledWith({
40+
...loadOpts,
41+
ui: mockUi,
42+
});
43+
});
44+
45+
it('calls load() with ui even when no options are passed', async () => {
46+
const clerk = createClerkClient({ publishableKey: 'pk_test_123' });
47+
48+
await clerk.load();
49+
50+
expect(mockLoad).toHaveBeenCalledOnce();
51+
expect(mockLoad).toHaveBeenCalledWith({ ui: mockUi });
52+
});
53+
});
54+
});

0 commit comments

Comments
 (0)