claude-code-wakatime 3.0.2 → 3.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-wakatime",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "WakaTime plugin for Claude Code",
5
5
  "bin": {
6
6
  "claude-code-wakatime": "dist/index.js"
@@ -8,8 +8,8 @@ import * as semver from 'semver';
8
8
  import * as which from 'which';
9
9
 
10
10
  import { Options, Setting } from './options';
11
- import { Utils } from './utils';
12
11
  import { Logger } from './logger';
12
+ import { buildOptions, isWindows } from './utils';
13
13
 
14
14
  enum osName {
15
15
  darwin = 'darwin',
@@ -49,7 +49,7 @@ export class Dependencies {
49
49
 
50
50
  const osname = this.osName();
51
51
  const arch = this.architecture();
52
- const ext = Utils.isWindows() ? '.exe' : '';
52
+ const ext = isWindows() ? '.exe' : '';
53
53
  const binary = `wakatime-cli-${osname}-${arch}${ext}`;
54
54
  this.cliLocation = path.join(this.resourcesLocation, binary);
55
55
 
@@ -59,7 +59,7 @@ export class Dependencies {
59
59
  public getCliLocationGlobal(): string | undefined {
60
60
  if (this.cliLocationGlobal) return this.cliLocationGlobal;
61
61
 
62
- const binaryName = `wakatime-cli${Utils.isWindows() ? '.exe' : ''}`;
62
+ const binaryName = `wakatime-cli${isWindows() ? '.exe' : ''}`;
63
63
  const path = which.sync(binaryName, { nothrow: true });
64
64
  if (path) {
65
65
  this.cliLocationGlobal = path;
@@ -96,10 +96,10 @@ export class Dependencies {
96
96
  }
97
97
 
98
98
  let args = ['--version'];
99
- const options = Utils.buildOptions();
99
+ const options = buildOptions();
100
100
  try {
101
101
  child_process.execFile(this.getCliLocation(), args, options, (error, _stdout, stderr) => {
102
- if (!(error != null)) {
102
+ if (!error) {
103
103
  let currentVersion = _stdout.toString().trim() + stderr.toString().trim();
104
104
  this.logger.debug(`Current wakatime-cli version is ${currentVersion}`);
105
105
 
@@ -212,7 +212,7 @@ export class Dependencies {
212
212
  this.unzip(zipFile, this.resourcesLocation, (unzipped) => {
213
213
  if (!unzipped) {
214
214
  this.restoreCli();
215
- } else if (!Utils.isWindows()) {
215
+ } else if (!isWindows()) {
216
216
  this.removeCli();
217
217
  const cli = this.getCliLocation();
218
218
  try {
@@ -221,7 +221,7 @@ export class Dependencies {
221
221
  } catch (e) {
222
222
  this.logger.warnException(e);
223
223
  }
224
- const ext = Utils.isWindows() ? '.exe' : '';
224
+ const ext = isWindows() ? '.exe' : '';
225
225
  const link = path.join(this.resourcesLocation, `wakatime-cli${ext}`);
226
226
  if (!this.isSymlink(link)) {
227
227
  try {
@@ -288,7 +288,7 @@ export class Dependencies {
288
288
  });
289
289
  } catch (e) {
290
290
  this.logger.warnException(e);
291
- callback();
291
+ error();
292
292
  }
293
293
  }
294
294
 
package/src/index.ts CHANGED
@@ -1,199 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from 'fs';
4
- import path from 'path';
5
- import os from 'os';
6
3
  import { execFile } from 'child_process';
7
4
  import { Options } from './options';
8
5
  import { VERSION } from './version';
9
6
  import { Dependencies } from './dependencies';
10
- import { Utils } from './utils';
11
- import { Logger, LogLevel } from './logger';
7
+ import { logger, Logger, LogLevel } from './logger';
8
+ import { Input } from './types';
9
+ import { buildOptions, formatArguments, getEntityFiles, parseInput, shouldSendHeartbeat, updateState } from './utils';
12
10
 
13
- const STATE_FILE = path.join(os.homedir(), '.wakatime', 'claude-code.json');
14
- const logger = new Logger();
15
11
  const options = new Options();
16
12
  const deps = new Dependencies(options, logger);
17
13
 
18
- type State = {
19
- lastHeartbeatAt?: number;
20
- };
21
-
22
- type Input = {
23
- session_id: string;
24
- transcript_path: string;
25
- cwd: string;
26
- hook_event_name: string;
27
- };
28
-
29
- function shouldSendHeartbeat(inp?: Input): boolean {
30
- if (inp?.hook_event_name === 'Stop') {
31
- return true;
32
- }
33
-
34
- try {
35
- const last = (JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8')) as State).lastHeartbeatAt ?? Utils.timestamp();
36
- return Utils.timestamp() - last >= 60;
37
- } catch {
38
- return true;
39
- }
40
- }
41
-
42
- function parseInput() {
43
- try {
44
- const stdinData = fs.readFileSync(0, 'utf-8');
45
- if (stdinData.trim()) {
46
- const input: Input = JSON.parse(stdinData);
47
- return input;
48
- }
49
- } catch (err) {
50
- console.error(err);
51
- }
52
- return undefined;
53
- }
54
-
55
- function getLastHeartbeat() {
56
- try {
57
- const stateData = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8')) as State;
58
- return stateData.lastHeartbeatAt ?? 0;
59
- } catch {
60
- return 0;
61
- }
62
- }
63
-
64
- function getModifiedFile(transcriptPath: string): string | undefined {
65
- try {
66
- if (!transcriptPath || !fs.existsSync(transcriptPath)) {
67
- return undefined;
68
- }
69
-
70
- const content = fs.readFileSync(transcriptPath, 'utf-8');
71
- const lines = content.split('\n');
72
- const fileLineChanges = new Map<string, number>();
73
-
74
- const lastHeartbeatAt = getLastHeartbeat();
75
- for (const line of lines) {
76
- if (line.trim()) {
77
- try {
78
- const logEntry = JSON.parse(line);
79
- if (!logEntry.timestamp) continue;
80
-
81
- const entryTimestamp = new Date(logEntry.timestamp).getTime() / 1000;
82
- if (entryTimestamp >= lastHeartbeatAt) {
83
- let filePath: string | undefined;
84
-
85
- // Check for file paths in tool use results
86
- if (logEntry.toolUse?.parameters?.file_path) {
87
- filePath = logEntry.toolUse.parameters.file_path;
88
- }
89
-
90
- // Check for file paths in tool use results for multi-edit
91
- if (logEntry.toolUse?.parameters?.edits) {
92
- filePath = logEntry.toolUse.parameters.file_path;
93
- }
94
-
95
- // Check for file paths and line changes in structured patch
96
- if (logEntry.toolUseResult?.structuredPatch) {
97
- const patches = logEntry.toolUseResult.structuredPatch;
98
- for (const patch of patches) {
99
- if (patch.file) {
100
- filePath = patch.file as string;
101
- if (patch.newLines !== undefined && patch.oldLines !== undefined) {
102
- const lineChanges = Math.abs(patch.newLines - patch.oldLines);
103
- fileLineChanges.set(filePath, (fileLineChanges.get(filePath) || 0) + lineChanges);
104
- }
105
- }
106
- }
107
- }
108
-
109
- if (filePath && !fileLineChanges.has(filePath)) {
110
- fileLineChanges.set(filePath, 0);
111
- }
112
- }
113
- } catch {
114
- // ignore
115
- }
116
- }
117
- }
118
-
119
- if (fileLineChanges.size === 0) {
120
- return undefined;
121
- }
122
-
123
- // Find file with most line changes
124
- let maxChanges = 0;
125
- let mostChangedFile: string | undefined;
126
- for (const [file, changes] of fileLineChanges.entries()) {
127
- if (changes > maxChanges) {
128
- maxChanges = changes;
129
- mostChangedFile = file;
130
- }
131
- }
132
-
133
- return mostChangedFile;
134
- } catch {
135
- return undefined;
136
- }
137
- }
138
-
139
- function calculateLineChanges(transcriptPath: string): number {
140
- try {
141
- if (!transcriptPath || !fs.existsSync(transcriptPath)) {
142
- return 0;
143
- }
144
-
145
- const content = fs.readFileSync(transcriptPath, 'utf-8');
146
- const lines = content.split('\n');
147
- let totalLineChanges = 0;
148
-
149
- const lastHeartbeatAt = getLastHeartbeat();
150
- for (const line of lines) {
151
- if (line.trim()) {
152
- try {
153
- const logEntry = JSON.parse(line);
154
-
155
- // Only count changes since last heartbeat
156
- if (logEntry.timestamp && logEntry.toolUseResult?.structuredPatch) {
157
- const entryTimestamp = new Date(logEntry.timestamp).getTime() / 1000;
158
- if (entryTimestamp >= lastHeartbeatAt) {
159
- const patches = logEntry.toolUseResult.structuredPatch;
160
- for (const patch of patches) {
161
- if (patch.newLines !== undefined && patch.oldLines !== undefined) {
162
- totalLineChanges += patch.newLines - patch.oldLines;
163
- }
164
- }
165
- }
166
- }
167
- } catch {
168
- // ignore
169
- }
170
- }
171
- }
172
-
173
- return totalLineChanges;
174
- } catch {
175
- return 0;
176
- }
177
- }
178
-
179
- function updateState() {
180
- fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
181
- fs.writeFileSync(STATE_FILE, JSON.stringify({ lastHeartbeatAt: Utils.timestamp() } as State, null, 2));
182
- }
183
-
184
- function getEntityFile(inp: Input | undefined): string | undefined {
185
- if (!inp?.transcript_path) return;
186
- return getModifiedFile(inp.transcript_path);
187
- }
188
-
189
- function sendHeartbeat(inp: Input | undefined) {
14
+ function sendHeartbeat(inp: Input | undefined): boolean {
190
15
  const projectFolder = inp?.cwd;
191
- try {
192
- const entity = getEntityFile(inp);
193
- if (!entity) return;
16
+ const { entities, claudeVersion } = getEntityFiles(inp);
17
+ if (entities.size === 0) return false;
194
18
 
195
- const wakatime_cli = deps.getCliLocation();
19
+ const wakatime_cli = deps.getCliLocation();
196
20
 
21
+ for (const [entity, lineChanges] of entities.entries()) {
22
+ logger.debug(`Entity: ${entity}`);
197
23
  const args: string[] = [
198
24
  '--entity',
199
25
  entity,
@@ -202,32 +28,29 @@ function sendHeartbeat(inp: Input | undefined) {
202
28
  '--category',
203
29
  'ai coding',
204
30
  '--plugin',
205
- `claude-code-wakatime/${VERSION}`,
31
+ `claude/${claudeVersion} claude-code-wakatime/${VERSION}`,
206
32
  ];
207
33
  if (projectFolder) {
208
34
  args.push('--project-folder');
209
35
  args.push(projectFolder);
210
36
  }
211
37
 
212
- if (inp?.transcript_path) {
213
- const lineChanges = calculateLineChanges(inp.transcript_path);
214
- if (lineChanges) {
215
- args.push('--ai-line-changes');
216
- args.push(lineChanges.toString());
217
- }
38
+ if (lineChanges) {
39
+ args.push('--ai-line-changes');
40
+ args.push(lineChanges.toString());
218
41
  }
219
42
 
220
- logger.debug(`Sending heartbeat: ${wakatime_cli} ${args}`);
43
+ logger.debug(`Sending heartbeat: ${formatArguments(wakatime_cli, args)}`);
221
44
 
222
- const execOptions = Utils.buildOptions();
45
+ const execOptions = buildOptions();
223
46
  execFile(wakatime_cli, args, execOptions, (error, stdout, stderr) => {
224
47
  const output = stdout.toString().trim() + stderr.toString().trim();
225
48
  if (output) logger.error(output);
226
49
  if (error) logger.error(error.toString());
227
50
  });
228
- } catch (err: any) {
229
- logger.errorException(err);
230
51
  }
52
+
53
+ return true;
231
54
  }
232
55
 
233
56
  function main() {
@@ -236,19 +59,18 @@ function main() {
236
59
  const debug = options.getSetting('settings', 'debug');
237
60
  logger.setLevel(debug === 'true' ? LogLevel.DEBUG : LogLevel.INFO);
238
61
 
239
- if (inp) {
240
- try {
241
- logger.debug(JSON.stringify(inp, null, 2));
242
- } catch (err) {
243
- // ignore
244
- }
245
- }
62
+ try {
63
+ if (inp) logger.debug(JSON.stringify(inp, null, 2));
246
64
 
247
- deps.checkAndInstallCli();
65
+ deps.checkAndInstallCli();
248
66
 
249
- if (shouldSendHeartbeat(inp)) {
250
- sendHeartbeat(inp);
251
- updateState();
67
+ if (shouldSendHeartbeat(inp)) {
68
+ if (sendHeartbeat(inp)) {
69
+ updateState();
70
+ }
71
+ }
72
+ } catch (err) {
73
+ logger.errorException(err);
252
74
  }
253
75
  }
254
76
 
package/src/logger.ts CHANGED
@@ -74,3 +74,11 @@ export class Logger {
74
74
  }
75
75
  }
76
76
  }
77
+
78
+ const global = globalThis as unknown as {
79
+ logger: Logger | undefined;
80
+ };
81
+
82
+ export const logger = global.logger ?? new Logger();
83
+
84
+ global.logger = logger;
package/src/options.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import * as child_process from 'child_process';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
-
5
- import { Utils } from './utils';
4
+ import { getHomeDirectory } from './utils';
6
5
 
7
6
  export interface Setting {
8
7
  key: string;
@@ -17,7 +16,7 @@ export class Options {
17
16
  public resourcesLocation: string;
18
17
 
19
18
  constructor() {
20
- const home = Utils.getHomeDirectory();
19
+ const home = getHomeDirectory();
21
20
  const wakaFolder = path.join(home, '.wakatime');
22
21
  try {
23
22
  if (!fs.existsSync(wakaFolder)) {
package/src/types.ts ADDED
@@ -0,0 +1,50 @@
1
+ export type State = {
2
+ lastHeartbeatAt?: number;
3
+ };
4
+
5
+ export type Input = {
6
+ session_id: string;
7
+ transcript_path: string;
8
+ cwd: string;
9
+ hook_event_name: string;
10
+ };
11
+
12
+ export type TranscriptLog = {
13
+ parentUuid?: string;
14
+ isSidechain?: boolean;
15
+ userType?: string;
16
+ cwd?: string;
17
+ sessionId?: string;
18
+ version?: string;
19
+ gitBranch?: string;
20
+ type?: string;
21
+ message?: {
22
+ role?: string;
23
+ content?: [
24
+ {
25
+ tool_use_id: string;
26
+ type: string;
27
+ content: string;
28
+ },
29
+ ];
30
+ };
31
+ uuid?: string;
32
+ timestamp?: string;
33
+ toolUseResult: {
34
+ filePath?: string;
35
+ oldString?: string;
36
+ newString?: string;
37
+ originalFile?: string;
38
+ structuredPatch?: [
39
+ {
40
+ oldStart: number;
41
+ oldLines: number;
42
+ newStart: number;
43
+ newLines: number;
44
+ lines: string[];
45
+ },
46
+ ];
47
+ userModified?: string;
48
+ replaceAll?: string;
49
+ };
50
+ };
package/src/utils.ts CHANGED
@@ -2,96 +2,143 @@ import * as fs from 'fs';
2
2
  import * as os from 'os';
3
3
  import * as child_process from 'child_process';
4
4
  import { StdioOptions } from 'child_process';
5
+ import { Input, State, TranscriptLog } from './types';
6
+ import path from 'path';
7
+ import { logger } from './logger';
5
8
 
6
- export class Utils {
7
- public static quote(str: string): string {
8
- if (str.includes(' ')) return `"${str.replace('"', '\\"')}"`;
9
- return str;
10
- }
9
+ const STATE_FILE = path.join(os.homedir(), '.wakatime', 'claude-code.json');
11
10
 
12
- public static apiKeyInvalid(key?: string): string {
13
- const err = 'Invalid api key... check https://wakatime.com/api-key for your key';
14
- if (!key) return err;
15
- const re = new RegExp('^(waka_)?[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$', 'i');
16
- if (!re.test(key)) return err;
17
- return '';
11
+ export function parseInput() {
12
+ try {
13
+ const stdinData = fs.readFileSync(0, 'utf-8');
14
+ if (stdinData.trim()) {
15
+ const input: Input = JSON.parse(stdinData);
16
+ return input;
17
+ }
18
+ } catch (err) {
19
+ console.error(err);
18
20
  }
21
+ return undefined;
22
+ }
19
23
 
20
- public static formatDate(date: Date): String {
21
- let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
22
- let ampm = 'AM';
23
- let hour = date.getHours();
24
- if (hour > 11) {
25
- ampm = 'PM';
26
- hour = hour - 12;
27
- }
28
- if (hour == 0) {
29
- hour = 12;
30
- }
31
- let minute = date.getMinutes();
32
- return `${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()} ${hour}:${minute < 10 ? `0${minute}` : minute} ${ampm}`;
24
+ export function shouldSendHeartbeat(inp?: Input): boolean {
25
+ if (inp?.hook_event_name === 'Stop') {
26
+ return true;
33
27
  }
34
28
 
35
- public static obfuscateKey(key: string): string {
36
- let newKey = '';
37
- if (key) {
38
- newKey = key;
39
- if (key.length > 4) newKey = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX' + key.substring(key.length - 4);
40
- }
41
- return newKey;
29
+ try {
30
+ const last = (JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8')) as State).lastHeartbeatAt ?? timestamp();
31
+ return timestamp() - last >= 60;
32
+ } catch {
33
+ return true;
42
34
  }
35
+ }
36
+
37
+ export function updateState() {
38
+ fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
39
+ fs.writeFileSync(STATE_FILE, JSON.stringify({ lastHeartbeatAt: timestamp() } as State, null, 2));
40
+ }
43
41
 
44
- public static wrapArg(arg: string): string {
45
- if (arg.indexOf(' ') > -1) return '"' + arg.replace(/"/g, '\\"') + '"';
46
- return arg;
42
+ export function getEntityFiles(inp: Input | undefined): { entities: Map<string, number>; claudeVersion: string } {
43
+ const entities = new Map<string, number>();
44
+ let claudeVersion = '';
45
+
46
+ const transcriptPath = inp?.transcript_path;
47
+ if (!transcriptPath || !fs.existsSync(transcriptPath)) {
48
+ return { entities, claudeVersion };
47
49
  }
48
50
 
49
- public static formatArguments(binary: string, args: string[]): string {
50
- let clone = args.slice(0);
51
- clone.unshift(this.wrapArg(binary));
52
- let newCmds: string[] = [];
53
- let lastCmd = '';
54
- for (let i = 0; i < clone.length; i++) {
55
- if (lastCmd == '--key') newCmds.push(this.wrapArg(this.obfuscateKey(clone[i])));
56
- else newCmds.push(this.wrapArg(clone[i]));
57
- lastCmd = clone[i];
51
+ const lastHeartbeatAt = getLastHeartbeat();
52
+
53
+ const content = fs.readFileSync(transcriptPath, 'utf-8');
54
+ for (const logLine of content.split('\n')) {
55
+ if (!logLine.trim()) continue;
56
+
57
+ try {
58
+ const log = JSON.parse(logLine) as TranscriptLog;
59
+ if (!log.timestamp) continue;
60
+
61
+ if (log.version) claudeVersion = log.version;
62
+
63
+ const timestamp = new Date(log.timestamp).getTime() / 1000;
64
+ if (timestamp < lastHeartbeatAt) continue;
65
+
66
+ const filePath = log.toolUseResult?.filePath;
67
+ if (!filePath) continue;
68
+
69
+ const patches = log.toolUseResult?.structuredPatch;
70
+ if (!patches) continue;
71
+
72
+ const lineChanges = patches.map((patch) => patch.newLines - patch.oldLines).reduce((p, c) => p + c, 0);
73
+
74
+ entities.set(filePath, (entities.get(filePath) ?? 0) + lineChanges);
75
+ } catch (err) {
76
+ logger.warnException(err);
58
77
  }
59
- return newCmds.join(' ');
60
78
  }
61
79
 
62
- public static apiUrlToDashboardUrl(url: string): string {
63
- url = url
64
- .replace('://api.', '://')
65
- .replace('/api/v1', '')
66
- .replace(/^api\./, '')
67
- .replace('/api', '');
68
- return url;
69
- }
80
+ return { entities, claudeVersion };
81
+ }
70
82
 
71
- public static isWindows(): boolean {
72
- return os.platform() === 'win32';
83
+ export function formatArguments(binary: string, args: string[]): string {
84
+ let clone = args.slice(0);
85
+ clone.unshift(wrapArg(binary));
86
+ let newCmds: string[] = [];
87
+ let lastCmd = '';
88
+ for (let i = 0; i < clone.length; i++) {
89
+ if (lastCmd == '--key') newCmds.push(wrapArg(obfuscateKey(clone[i])));
90
+ else newCmds.push(wrapArg(clone[i]));
91
+ lastCmd = clone[i];
73
92
  }
93
+ return newCmds.join(' ');
94
+ }
74
95
 
75
- public static getHomeDirectory(): string {
76
- let home = process.env.WAKATIME_HOME;
77
- if (home && home.trim() && fs.existsSync(home.trim())) return home.trim();
78
- return process.env[this.isWindows() ? 'USERPROFILE' : 'HOME'] || process.cwd();
96
+ export function isWindows(): boolean {
97
+ return os.platform() === 'win32';
98
+ }
99
+
100
+ export function getHomeDirectory(): string {
101
+ let home = process.env.WAKATIME_HOME;
102
+ if (home && home.trim() && fs.existsSync(home.trim())) return home.trim();
103
+ return process.env[isWindows() ? 'USERPROFILE' : 'HOME'] || process.cwd();
104
+ }
105
+
106
+ export function buildOptions(stdin?: boolean): Object {
107
+ const options: child_process.ExecFileOptions = {
108
+ windowsHide: true,
109
+ };
110
+ if (stdin) {
111
+ (options as any).stdio = ['pipe', 'pipe', 'pipe'] as StdioOptions;
112
+ }
113
+ if (!isWindows() && !process.env.WAKATIME_HOME && !process.env.HOME) {
114
+ options['env'] = { ...process.env, WAKATIME_HOME: getHomeDirectory() };
79
115
  }
116
+ return options;
117
+ }
80
118
 
81
- public static buildOptions(stdin?: boolean): Object {
82
- const options: child_process.ExecFileOptions = {
83
- windowsHide: true,
84
- };
85
- if (stdin) {
86
- (options as any).stdio = ['pipe', 'pipe', 'pipe'] as StdioOptions;
87
- }
88
- if (!this.isWindows() && !process.env.WAKATIME_HOME && !process.env.HOME) {
89
- options['env'] = { ...process.env, WAKATIME_HOME: this.getHomeDirectory() };
90
- }
91
- return options;
119
+ function getLastHeartbeat() {
120
+ try {
121
+ const stateData = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8')) as State;
122
+ return stateData.lastHeartbeatAt ?? 0;
123
+ } catch {
124
+ return 0;
92
125
  }
126
+ }
127
+
128
+ function timestamp() {
129
+ return Date.now() / 1000;
130
+ }
131
+
132
+ function wrapArg(arg: string): string {
133
+ if (arg.indexOf(' ') > -1) return '"' + arg.replace(/"/g, '\\"') + '"';
134
+ return arg;
135
+ }
93
136
 
94
- public static timestamp() {
95
- return Date.now() / 1000;
137
+ function obfuscateKey(key: string): string {
138
+ let newKey = '';
139
+ if (key) {
140
+ newKey = key;
141
+ if (key.length > 4) newKey = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX' + key.substring(key.length - 4);
96
142
  }
143
+ return newKey;
97
144
  }