kap-r2 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  interface KapConfig {
2
2
  get(key: string): string;
3
3
  }
4
+ interface RequestOptions {
5
+ method?: string;
6
+ headers?: Record<string, string | number | undefined>;
7
+ body?: Buffer;
8
+ }
4
9
  interface KapContext {
5
10
  filePath(): Promise<string>;
6
11
  config: KapConfig;
12
+ request(url: string, options?: RequestOptions): Promise<{
13
+ body: string;
14
+ }>;
7
15
  setProgress(text: string, percentage?: number): void;
8
16
  copyToClipboard(text: string): void;
9
17
  notify(text: string, action?: () => void): void;
@@ -51,6 +59,13 @@ export declare const shareServices: {
51
59
  default: string;
52
60
  required: boolean;
53
61
  };
62
+ addTimestamp: {
63
+ title: string;
64
+ description: string;
65
+ type: string;
66
+ default: boolean;
67
+ required: boolean;
68
+ };
54
69
  };
55
70
  }[];
56
71
  export {};
package/dist/index.js CHANGED
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.shareServices = void 0;
7
7
  const promises_1 = require("fs/promises");
8
8
  const path_1 = __importDefault(require("path"));
9
- const https_1 = __importDefault(require("https"));
10
9
  const aws4_1 = __importDefault(require("aws4"));
11
10
  const contentTypes = new Map([
12
11
  [".gif", "image/gif"],
@@ -37,29 +36,31 @@ const action = async (context) => {
37
36
  context.cancel();
38
37
  return;
39
38
  }
40
- if (!publicUrl.startsWith("https://")) {
41
- context.notify("Public URL must start with https://");
39
+ if (!/^https:\/\/.+/.test(publicUrl)) {
40
+ context.notify("Public URL must be a valid https:// URL");
42
41
  context.cancel();
43
42
  return;
44
43
  }
45
44
  const filePath = await context.filePath();
46
- const fileStats = await (0, promises_1.stat)(filePath);
47
45
  const fileBuffer = await (0, promises_1.readFile)(filePath);
48
46
  context.setProgress("Uploading to R2…", 0);
49
- const filename = path_1.default.basename(filePath);
47
+ const addTimestamp = context.config.get("addTimestamp") === "true";
48
+ const originalFilename = path_1.default.basename(filePath);
49
+ const filename = addTimestamp ? `${Date.now()}-${originalFilename}` : originalFilename;
50
50
  const key = directory ? path_1.default.posix.join(directory, filename) : filename;
51
51
  const extension = path_1.default.extname(filename);
52
52
  const contentType = contentTypes.get(extension) || "application/octet-stream";
53
53
  const host = `${accountId}.r2.cloudflarestorage.com`;
54
54
  const encodedKey = key.split("/").map(encodeURIComponent).join("/");
55
55
  const fullPath = `/${bucket}/${encodedKey}`;
56
- const request = aws4_1.default.sign({
56
+ const url = `https://${host}${fullPath}`;
57
+ const signed = aws4_1.default.sign({
57
58
  host,
58
59
  path: fullPath,
59
60
  method: "PUT",
60
61
  headers: {
61
62
  "Content-Type": contentType,
62
- "Content-Length": fileStats.size,
63
+ "Content-Length": fileBuffer.length,
63
64
  },
64
65
  body: fileBuffer,
65
66
  service: "s3",
@@ -69,28 +70,11 @@ const action = async (context) => {
69
70
  secretAccessKey,
70
71
  });
71
72
  try {
72
- await new Promise((resolve, reject) => {
73
- const req = https_1.default.request({
74
- hostname: host,
75
- path: fullPath,
76
- method: "PUT",
77
- headers: request.headers,
78
- }, (res) => {
79
- const chunks = [];
80
- res.on("data", (chunk) => chunks.push(chunk));
81
- res.on("end", () => {
82
- const body = Buffer.concat(chunks).toString();
83
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
84
- resolve();
85
- }
86
- else {
87
- reject(new Error(`HTTP ${res.statusCode}: ${body}`));
88
- }
89
- });
90
- });
91
- req.on("error", reject);
92
- context.setProgress("Uploading to R2…", 0.5);
93
- req.end(fileBuffer);
73
+ context.setProgress("Uploading to R2…", 0.5);
74
+ await context.request(url, {
75
+ method: "PUT",
76
+ headers: signed.headers,
77
+ body: fileBuffer,
94
78
  });
95
79
  const baseUrl = publicUrl.replace(/\/$/, "");
96
80
  const uploadUrl = `${baseUrl}/${encodedKey}`;
@@ -118,7 +102,7 @@ const r2 = {
118
102
  title: "Share to R2",
119
103
  formats: ["gif", "mp4", "webm", "apng"],
120
104
  action,
121
- configDescription: "Configure your Cloudflare R2 credentials. Get your API tokens from the Cloudflare dashboard under R2 > Manage R2 API Tokens.",
105
+ configDescription: "Configure your Cloudflare R2 credentials. Get your API tokens from https://dash.cloudflare.com/?to=/:account/r2/api-tokens",
122
106
  config: {
123
107
  accountId: {
124
108
  title: "Account ID",
@@ -156,6 +140,13 @@ const r2 = {
156
140
  default: "",
157
141
  required: true,
158
142
  },
143
+ addTimestamp: {
144
+ title: "Add Timestamp to Filename",
145
+ description: "Prefix filenames with timestamp to avoid overwrites",
146
+ type: "boolean",
147
+ default: false,
148
+ required: false,
149
+ },
159
150
  },
160
151
  };
161
152
  exports.shareServices = [r2];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kap-r2",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Share on Cloudflare R2",
5
5
  "license": "MIT",
6
6
  "repository": "tristanremy/kap-r2",
@@ -29,12 +29,15 @@
29
29
  "devDependencies": {
30
30
  "@types/aws4": "^1.11.6",
31
31
  "@types/node": "^20.0.0",
32
+ "ava": "^5.3.1",
33
+ "kap-plugin-test": "^0.5.0",
32
34
  "typescript": "^5.0.0"
33
35
  },
34
36
  "engines": {
35
37
  "node": ">=14"
36
38
  },
37
39
  "scripts": {
38
- "build": "tsc"
40
+ "build": "tsc",
41
+ "test": "pnpm build && ava"
39
42
  }
40
43
  }
package/readme.md CHANGED
@@ -1,110 +1,30 @@
1
1
  # kap-r2
2
2
 
3
- > [Kap](https://github.com/wulkano/Kap) plugin - Upload recordings to Cloudflare R2
4
-
5
- Instantly share your Kap screen recordings via Cloudflare R2 storage. After recording, your file is uploaded and the public URL is automatically copied to your clipboard.
3
+ > [Kap](https://github.com/wulkano/kap) plugin - Share on [Cloudflare R2](https://www.cloudflare.com/products/r2/)
6
4
 
7
5
  ## Install
8
6
 
9
- In the `Kap` menu, go to `Preferences…`, select the `Plugins` pane, search for `kap-r2`, and click `Install`.
10
-
11
- ## Configuration
12
-
13
- After installing, click the gear icon next to the plugin to configure it.
14
-
15
- | Setting | Description |
16
- |---------|-------------|
17
- | **Account ID** | Your Cloudflare account ID (32-character hex string) |
18
- | **Access Key ID** | R2 API token access key |
19
- | **Secret Access Key** | R2 API token secret |
20
- | **Bucket Name** | Name of your R2 bucket |
21
- | **Directory** | Optional subfolder path (e.g., `recordings/`) |
22
- | **Public URL** | Your bucket's public URL (must start with `https://`) |
23
-
24
- ## Setup Guide
25
-
26
- ### Step 1: Find Your Account ID
27
-
28
- 1. Log in to the [Cloudflare Dashboard](https://dash.cloudflare.com)
29
- 2. Select your account
30
- 3. Go to **R2** in the left sidebar
31
- 4. Your **Account ID** is displayed in the right sidebar under "Account details"
32
-
33
- ### Step 2: Create an R2 Bucket
34
-
35
- 1. In the R2 section, click **Create bucket**
36
- 2. Enter a bucket name (lowercase letters, numbers, and hyphens only)
37
- 3. Click **Create bucket**
38
-
39
- ### Step 3: Enable Public Access
40
-
41
- You need to make your bucket publicly accessible. Choose one option:
42
-
43
- #### Option A: R2.dev Subdomain (Easiest)
44
-
45
- 1. Click on your bucket
46
- 2. Go to **Settings** tab
47
- 3. Under "Public access", find **R2.dev subdomain**
48
- 4. Click **Allow Access**
49
- 5. Copy the provided URL (e.g., `https://pub-abc123.r2.dev`)
50
-
51
- #### Option B: Custom Domain
52
-
53
- 1. Click on your bucket
54
- 2. Go to **Settings** tab
55
- 3. Under "Public access", click **Connect Domain**
56
- 4. Follow the instructions to add your custom domain
57
- 5. Use your domain as the Public URL (e.g., `https://cdn.yourdomain.com`)
58
-
59
- ### Step 4: Create API Credentials
60
-
61
- 1. In the R2 section, click **Manage R2 API Tokens** (top right)
62
- 2. Click **Create API token**
63
- 3. Give it a name (e.g., "Kap Upload")
64
- 4. Under "Permissions", select **Object Read & Write**
65
- 5. Under "Specify bucket(s)", choose your bucket or select "Apply to all buckets"
66
- 6. Click **Create API Token**
67
- 7. **Important**: Copy both the **Access Key ID** and **Secret Access Key** immediately — the secret is only shown once!
68
-
69
- ### Step 5: Configure the Plugin
70
-
71
- 1. Open Kap → Preferences → Plugins
72
- 2. Click the gear icon next to kap-r2
73
- 3. Fill in all the fields:
74
- - **Account ID**: The 32-character ID from Step 1
75
- - **Access Key ID**: From Step 4
76
- - **Secret Access Key**: From Step 4
77
- - **Bucket Name**: The name you chose in Step 2
78
- - **Directory**: Leave empty or enter a folder name (optional)
79
- - **Public URL**: The URL from Step 3 (must start with `https://`)
7
+ In the `Kap` menu, go to `Preferences…`, select the `Plugins` pane, find this plugin, and click `Install`.
80
8
 
81
9
  ## Usage
82
10
 
83
- 1. Record your screen with Kap
84
- 2. Choose an export format (GIF, MP4, WebM, or APNG)
85
- 3. Click **Share to R2**
86
- 4. Wait for the upload to complete
87
- 5. The public URL is automatically copied to your clipboard
88
-
89
- ## Troubleshooting
90
-
91
- ### "Export cancelled" immediately
92
- - Make sure all required fields are configured in the plugin settings
93
- - Check that your Account ID is exactly 32 characters (lowercase hex)
94
- - Verify your Public URL starts with `https://`
95
-
96
- ### Upload fails with authentication error
97
- - Double-check your Access Key ID and Secret Access Key
98
- - Make sure your API token has "Object Read & Write" permission
99
- - Verify the token has access to the specified bucket
11
+ 1. Configure your R2 credentials by clicking the gear icon next to the plugin
12
+ 2. After recording, select an export format and click `Share to R2`
13
+ 3. The public URL is copied to your clipboard
100
14
 
101
- ### File not appearing in bucket
102
- - Check the Cloudflare R2 dashboard to see if the file was uploaded
103
- - Verify your bucket name is correct (case-sensitive)
15
+ ## Configuration
104
16
 
105
- ### URL doesn't work
106
- - Make sure public access is enabled on your bucket
107
- - Verify the Public URL matches your R2.dev subdomain or custom domain exactly
17
+ | Setting | Description |
18
+ |---------|-------------|
19
+ | Account ID | Your Cloudflare account ID (32-character hex) |
20
+ | Access Key ID | R2 API token access key |
21
+ | Secret Access Key | R2 API token secret |
22
+ | Bucket Name | Your R2 bucket name |
23
+ | Directory | Optional subfolder path |
24
+ | Public URL | Your bucket's public URL (e.g., `https://pub-xxx.r2.dev`) |
25
+ | Add Timestamp to Filename | Prefix filenames with timestamp to avoid overwrites |
26
+
27
+ Get your API credentials from the [Cloudflare R2 API Tokens page](https://dash.cloudflare.com/?to=/:account/r2/api-tokens).
108
28
 
109
29
  ## License
110
30