@hasna/contacts 0.8.1 → 0.9.1
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/README.md +10 -0
- package/dist/browser/cli.d.ts +4 -0
- package/dist/browser/cli.d.ts.map +1 -0
- package/dist/browser/identity.d.ts +3 -0
- package/dist/browser/identity.d.ts.map +1 -0
- package/dist/browser/install.d.ts +32 -0
- package/dist/browser/install.d.ts.map +1 -0
- package/dist/browser/native.d.ts +3 -0
- package/dist/browser/native.d.ts.map +1 -0
- package/dist/browser/native.js +822 -0
- package/dist/browser/protocol.d.ts +26 -0
- package/dist/browser/protocol.d.ts.map +1 -0
- package/dist/browser/values.d.ts +18 -0
- package/dist/browser/values.d.ts.map +1 -0
- package/dist/cli/commands/core.d.ts.map +1 -1
- package/dist/cli/index.js +408 -127
- package/dist/cli/status-domain.preload.d.ts +1 -1
- package/dist/cli/status-domain.preload.d.ts.map +1 -1
- package/dist/cli/status-fixture.d.ts +17 -0
- package/dist/cli/status-fixture.d.ts.map +1 -0
- package/dist/index.js +43 -4
- package/dist/lib/audience-contract.d.ts +9 -9
- package/dist/lib/compact-output.d.ts +67 -0
- package/dist/lib/compact-output.d.ts.map +1 -0
- package/dist/mcp/handlers/core.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +2 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +378 -194
- package/dist/mcp/profile.d.ts +8 -0
- package/dist/mcp/profile.d.ts.map +1 -0
- package/dist/mcp/register-tools.d.ts +3 -1
- package/dist/mcp/register-tools.d.ts.map +1 -1
- package/dist/mcp/storage-tools.d.ts +2 -1
- package/dist/mcp/storage-tools.d.ts.map +1 -1
- package/dist/mcp/tools.d.ts +7 -45
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/server/index.js +30 -16
- package/dist/server/pg-store.d.ts +1 -0
- package/dist/server/pg-store.d.ts.map +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/docs/chrome-autofill.md +45 -0
- package/extension/background.js +284 -0
- package/extension/content.js +392 -0
- package/extension/detect.js +228 -0
- package/extension/fill.js +112 -0
- package/extension/icons/128.png +0 -0
- package/extension/icons/16.png +0 -0
- package/extension/icons/32.png +0 -0
- package/extension/icons/48.png +0 -0
- package/extension/icons/icon.svg +7 -0
- package/extension/manifest.json +54 -0
- package/extension/popup.css +71 -0
- package/extension/popup.html +50 -0
- package/extension/popup.js +166 -0
- package/package.json +9 -5
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/browser/native.ts
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
|
|
7
|
+
// src/sdk/index.ts
|
|
8
|
+
import { resolveCredential as resolveCredential2 } from "@hasna/contracts/client";
|
|
9
|
+
|
|
10
|
+
// src/cloud/http-storage.ts
|
|
11
|
+
import {
|
|
12
|
+
ClientTransportConfigurationError,
|
|
13
|
+
createHasnaHttpTransport,
|
|
14
|
+
resolveCredential,
|
|
15
|
+
resolveClientTransport as resolveSharedClientTransport
|
|
16
|
+
} from "@hasna/contracts/client";
|
|
17
|
+
|
|
18
|
+
// src/cloud/client-config.ts
|
|
19
|
+
import { createHash } from "crypto";
|
|
20
|
+
import { readFileSync, statSync } from "fs";
|
|
21
|
+
import { clientTransportEnvKeys, credentialDiskSourceList, toV1BaseUrl } from "@hasna/contracts/client";
|
|
22
|
+
function invalid() {
|
|
23
|
+
throw new Error("CONTACTS_CLIENT_CONFIG_INVALID: blank, conflicting, or unstable client configuration; no request was sent.");
|
|
24
|
+
}
|
|
25
|
+
function checkAliases(values) {
|
|
26
|
+
const keys = clientTransportEnvKeys("contacts");
|
|
27
|
+
for (const group of [keys.apiUrlKeys, keys.apiKeyKeys]) {
|
|
28
|
+
const defined = group.filter((key) => values[key] !== undefined);
|
|
29
|
+
const normalized = defined.map((key) => {
|
|
30
|
+
const value = values[key].trim();
|
|
31
|
+
if (!value)
|
|
32
|
+
invalid();
|
|
33
|
+
if (group === keys.apiUrlKeys) {
|
|
34
|
+
try {
|
|
35
|
+
return toV1BaseUrl(value);
|
|
36
|
+
} catch {
|
|
37
|
+
invalid();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
});
|
|
42
|
+
if (new Set(normalized).size > 1)
|
|
43
|
+
invalid();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function checkDiskAliases(text) {
|
|
47
|
+
const values = {};
|
|
48
|
+
const wanted = new Set(Object.values(clientTransportEnvKeys("contacts")).flat());
|
|
49
|
+
for (const line of text.split(/\r?\n/)) {
|
|
50
|
+
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/.exec(line);
|
|
51
|
+
if (!match || !wanted.has(match[1]))
|
|
52
|
+
continue;
|
|
53
|
+
let value = match[2].trim();
|
|
54
|
+
if (value.startsWith('"') || value.startsWith("'")) {
|
|
55
|
+
if (value.length < 2 || value.at(-1) !== value[0])
|
|
56
|
+
invalid();
|
|
57
|
+
value = value.slice(1, -1);
|
|
58
|
+
}
|
|
59
|
+
if (values[match[1]] !== undefined && values[match[1]] !== value)
|
|
60
|
+
invalid();
|
|
61
|
+
values[match[1]] = value;
|
|
62
|
+
}
|
|
63
|
+
checkAliases(values);
|
|
64
|
+
}
|
|
65
|
+
function clientConfigurationStamp(env) {
|
|
66
|
+
checkAliases(env);
|
|
67
|
+
const digest = createHash("sha256");
|
|
68
|
+
digest.update(JSON.stringify(Object.entries(env).sort(([a], [b]) => a.localeCompare(b))));
|
|
69
|
+
const profile = env.HASNA_PROFILE?.trim();
|
|
70
|
+
if (profile && !/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(profile))
|
|
71
|
+
invalid();
|
|
72
|
+
const paths = [
|
|
73
|
+
...credentialDiskSourceList("contacts", env),
|
|
74
|
+
...profile ? credentialDiskSourceList("contacts", env, profile) : []
|
|
75
|
+
];
|
|
76
|
+
for (const { path } of paths) {
|
|
77
|
+
digest.update(path);
|
|
78
|
+
try {
|
|
79
|
+
const before = statSync(path, { bigint: true });
|
|
80
|
+
if (!before.isFile() || before.size > 65536n)
|
|
81
|
+
invalid();
|
|
82
|
+
const contents = readFileSync(path);
|
|
83
|
+
const after = statSync(path, { bigint: true });
|
|
84
|
+
const identity = (s) => `${s.dev}:${s.ino}:${s.size}:${s.mtimeNs}:${s.ctimeNs}`;
|
|
85
|
+
if (identity(before) !== identity(after))
|
|
86
|
+
invalid();
|
|
87
|
+
checkDiskAliases(contents.toString("utf8"));
|
|
88
|
+
digest.update(identity(after)).update(contents);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error.code !== "ENOENT")
|
|
91
|
+
invalid();
|
|
92
|
+
digest.update("absent");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return digest.digest("hex");
|
|
96
|
+
}
|
|
97
|
+
function assertConfigurationUnchanged(env, expected) {
|
|
98
|
+
if (clientConfigurationStamp(env) !== expected)
|
|
99
|
+
invalid();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/cloud/resolver-inputs.ts
|
|
103
|
+
import {
|
|
104
|
+
clientTransportEnvKeys as clientTransportEnvKeys2,
|
|
105
|
+
credentialOverrideEnvKey,
|
|
106
|
+
credentialPointerEnvKey,
|
|
107
|
+
CREDENTIAL_PROFILE_ENV_KEY
|
|
108
|
+
} from "@hasna/contracts/client";
|
|
109
|
+
var CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
110
|
+
function isAmbientContactsEnv(env) {
|
|
111
|
+
if (typeof process !== "undefined" && env === process.env)
|
|
112
|
+
return true;
|
|
113
|
+
return env[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
|
|
114
|
+
}
|
|
115
|
+
function contactsResolverCredentials(env, credentials = {}) {
|
|
116
|
+
const keychain = { ...credentials.keychain };
|
|
117
|
+
if (keychain.enabled === undefined && keychain.run === undefined) {
|
|
118
|
+
keychain.enabled = isAmbientContactsEnv(env);
|
|
119
|
+
}
|
|
120
|
+
return { ...credentials, keychain };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/cloud/http-storage.ts
|
|
124
|
+
var RETIRED_CLIENT_SELECTOR_KEYS = [
|
|
125
|
+
"HASNA_CONTACTS_STORAGE_MODE",
|
|
126
|
+
"CONTACTS_STORAGE_MODE",
|
|
127
|
+
"HASNA_CONTACTS_MODE",
|
|
128
|
+
"CONTACTS_MODE",
|
|
129
|
+
"HASNA_CONTACTS_DB_PATH",
|
|
130
|
+
"CONTACTS_DB_PATH",
|
|
131
|
+
"HASNA_CONTACTS_DATABASE_URL",
|
|
132
|
+
"CONTACTS_DATABASE_URL"
|
|
133
|
+
];
|
|
134
|
+
function configuredKeys(env, keys) {
|
|
135
|
+
return keys.filter((key) => env[key] !== undefined && env[key].trim().length > 0);
|
|
136
|
+
}
|
|
137
|
+
function assertNoRetiredClientSelectors(env) {
|
|
138
|
+
const found = configuredKeys(env, RETIRED_CLIENT_SELECTOR_KEYS);
|
|
139
|
+
if (found.length === 0)
|
|
140
|
+
return;
|
|
141
|
+
throw new ContactsClientConfigurationError("RETIRED_CONTACTS_CLIENT_SELECTOR", `Contacts clients use only HASNA_CONTACTS_API_URL plus an API key resolved by @hasna/contracts. ` + `Remove retired client selector${found.length === 1 ? "" : "s"}: ${found.join(", ")}. ` + "PostgreSQL configuration belongs only on contacts-serve; local SQLite is available only through the explicit legacy migration command.");
|
|
142
|
+
}
|
|
143
|
+
function assertHttpsBaseUrl(baseUrl) {
|
|
144
|
+
const url = new URL(baseUrl);
|
|
145
|
+
if (url.protocol !== "https:") {
|
|
146
|
+
throw new ContactsClientConfigurationError("CONTACTS_API_HTTPS_REQUIRED", "HASNA_CONTACTS_API_URL must use HTTPS. Plain HTTP and local-store fallback are disabled for contacts clients.");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
class ContactsClientConfigurationError extends Error {
|
|
151
|
+
code;
|
|
152
|
+
constructor(code, message) {
|
|
153
|
+
super(`${code}: ${message}`);
|
|
154
|
+
this.code = code;
|
|
155
|
+
this.name = "ContactsClientConfigurationError";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function unconfiguredResolution(issue, resolution, warning = null) {
|
|
159
|
+
return {
|
|
160
|
+
transport: "unconfigured",
|
|
161
|
+
baseUrl: null,
|
|
162
|
+
apiUrlSource: resolution?.apiUrlSource ?? null,
|
|
163
|
+
apiKeyPresent: resolution?.apiKeyPresent ?? false,
|
|
164
|
+
apiKeySource: resolution?.apiKeySource ?? null,
|
|
165
|
+
apiKeyTier: resolution?.apiKeyTier ?? null,
|
|
166
|
+
configured: false,
|
|
167
|
+
misconfigured: true,
|
|
168
|
+
issue,
|
|
169
|
+
warning
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function resolveContactsClientTransport(name, env = process.env, credentials = {}) {
|
|
173
|
+
if (name !== "contacts") {
|
|
174
|
+
throw new ContactsClientConfigurationError("CONTACTS_CLIENT_NAME_INVALID", "This resolver only accepts the contacts app slug.");
|
|
175
|
+
}
|
|
176
|
+
assertNoRetiredClientSelectors(env);
|
|
177
|
+
const stamp = clientConfigurationStamp(env);
|
|
178
|
+
let resolution;
|
|
179
|
+
try {
|
|
180
|
+
resolution = resolveSharedClientTransport(name, env, {
|
|
181
|
+
credentials: contactsResolverCredentials(env, credentials)
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
assertConfigurationUnchanged(env, stamp);
|
|
185
|
+
if (error instanceof ClientTransportConfigurationError) {
|
|
186
|
+
return unconfiguredResolution(error.message, null);
|
|
187
|
+
}
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
assertConfigurationUnchanged(env, stamp);
|
|
191
|
+
try {
|
|
192
|
+
assertHttpsBaseUrl(resolution.baseUrl);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return {
|
|
195
|
+
...unconfiguredResolution(error instanceof Error ? error.message : String(error), resolution),
|
|
196
|
+
apiKeyPresent: resolution.apiKeyPresent
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
transport: "https",
|
|
201
|
+
baseUrl: resolution.baseUrl,
|
|
202
|
+
apiUrlSource: resolution.apiUrlSource,
|
|
203
|
+
apiKeyPresent: resolution.apiKeyPresent,
|
|
204
|
+
apiKeySource: resolution.apiKeySource,
|
|
205
|
+
apiKeyTier: resolution.apiKeyTier,
|
|
206
|
+
configured: true,
|
|
207
|
+
misconfigured: false,
|
|
208
|
+
issue: null,
|
|
209
|
+
warning: resolution.warning
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/sdk/v1.generated.ts
|
|
214
|
+
class ApiError extends Error {
|
|
215
|
+
status;
|
|
216
|
+
body;
|
|
217
|
+
constructor(status, message, body) {
|
|
218
|
+
super(message);
|
|
219
|
+
this.status = status;
|
|
220
|
+
this.body = body;
|
|
221
|
+
this.name = "ApiError";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
class ContactsV1Client {
|
|
226
|
+
baseUrl;
|
|
227
|
+
apiKey;
|
|
228
|
+
fetchImpl;
|
|
229
|
+
baseHeaders;
|
|
230
|
+
constructor(options) {
|
|
231
|
+
if (!options.baseUrl)
|
|
232
|
+
throw new Error("ContactsV1Client requires a baseUrl.");
|
|
233
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
234
|
+
this.apiKey = options.apiKey;
|
|
235
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
236
|
+
this.baseHeaders = options.headers ?? {};
|
|
237
|
+
}
|
|
238
|
+
async request(method, path, opts) {
|
|
239
|
+
const url = new URL(this.baseUrl + path);
|
|
240
|
+
if (opts.query) {
|
|
241
|
+
for (const [key, value] of Object.entries(opts.query)) {
|
|
242
|
+
if (value !== undefined && value !== null)
|
|
243
|
+
url.searchParams.set(key, String(value));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const headers = { Accept: "application/json", ...this.baseHeaders, ...opts.init?.headers };
|
|
247
|
+
if (this.apiKey)
|
|
248
|
+
headers["x-api-key"] = this.apiKey;
|
|
249
|
+
let payload;
|
|
250
|
+
if (opts.body !== undefined) {
|
|
251
|
+
headers["Content-Type"] = "application/json";
|
|
252
|
+
payload = JSON.stringify(opts.body);
|
|
253
|
+
}
|
|
254
|
+
const response = await this.fetchImpl(url.toString(), { ...opts.init, method, headers, body: payload });
|
|
255
|
+
const text = await response.text();
|
|
256
|
+
const data = text ? (() => {
|
|
257
|
+
try {
|
|
258
|
+
return JSON.parse(text);
|
|
259
|
+
} catch {
|
|
260
|
+
return text;
|
|
261
|
+
}
|
|
262
|
+
})() : undefined;
|
|
263
|
+
if (!response.ok) {
|
|
264
|
+
throw new ApiError(response.status, `${method} ${path} failed: ${response.status}`, data);
|
|
265
|
+
}
|
|
266
|
+
return data;
|
|
267
|
+
}
|
|
268
|
+
async listCompanies(query, init) {
|
|
269
|
+
return this.request("GET", `/v1/companies`, {
|
|
270
|
+
body: undefined,
|
|
271
|
+
query,
|
|
272
|
+
init
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async createCompany(body, init) {
|
|
276
|
+
return this.request("POST", `/v1/companies`, {
|
|
277
|
+
body,
|
|
278
|
+
query: undefined,
|
|
279
|
+
init
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
async getCompany(id, init) {
|
|
283
|
+
return this.request("GET", `/v1/companies/${encodeURIComponent(String(id))}`, {
|
|
284
|
+
body: undefined,
|
|
285
|
+
query: undefined,
|
|
286
|
+
init
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
async deleteCompany(id, init) {
|
|
290
|
+
return this.request("DELETE", `/v1/companies/${encodeURIComponent(String(id))}`, {
|
|
291
|
+
body: undefined,
|
|
292
|
+
query: undefined,
|
|
293
|
+
init
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
async updateCompany(id, body, init) {
|
|
297
|
+
return this.request("PATCH", `/v1/companies/${encodeURIComponent(String(id))}`, {
|
|
298
|
+
body,
|
|
299
|
+
query: undefined,
|
|
300
|
+
init
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
async listContacts(query, init) {
|
|
304
|
+
return this.request("GET", `/v1/contacts`, {
|
|
305
|
+
body: undefined,
|
|
306
|
+
query,
|
|
307
|
+
init
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
async createContact(body, init) {
|
|
311
|
+
return this.request("POST", `/v1/contacts`, {
|
|
312
|
+
body,
|
|
313
|
+
query: undefined,
|
|
314
|
+
init
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
async getContactProjectIds(contactId, init) {
|
|
318
|
+
return this.request("GET", `/v1/contacts/${encodeURIComponent(String(contactId))}/projects`, {
|
|
319
|
+
body: undefined,
|
|
320
|
+
query: undefined,
|
|
321
|
+
init
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
async setContactProjects(contactId, body, init) {
|
|
325
|
+
return this.request("PUT", `/v1/contacts/${encodeURIComponent(String(contactId))}/projects`, {
|
|
326
|
+
body,
|
|
327
|
+
query: undefined,
|
|
328
|
+
init
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
async linkContactToProject(contactId, projectId, init) {
|
|
332
|
+
return this.request("PUT", `/v1/contacts/${encodeURIComponent(String(contactId))}/projects/${encodeURIComponent(String(projectId))}`, {
|
|
333
|
+
body: undefined,
|
|
334
|
+
query: undefined,
|
|
335
|
+
init
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
async unlinkContactFromProject(contactId, projectId, init) {
|
|
339
|
+
return this.request("DELETE", `/v1/contacts/${encodeURIComponent(String(contactId))}/projects/${encodeURIComponent(String(projectId))}`, {
|
|
340
|
+
body: undefined,
|
|
341
|
+
query: undefined,
|
|
342
|
+
init
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
async addTagToContact(contactId, tagId, init) {
|
|
346
|
+
return this.request("PUT", `/v1/contacts/${encodeURIComponent(String(contactId))}/tags/${encodeURIComponent(String(tagId))}`, {
|
|
347
|
+
body: undefined,
|
|
348
|
+
query: undefined,
|
|
349
|
+
init
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async removeTagFromContact(contactId, tagId, init) {
|
|
353
|
+
return this.request("DELETE", `/v1/contacts/${encodeURIComponent(String(contactId))}/tags/${encodeURIComponent(String(tagId))}`, {
|
|
354
|
+
body: undefined,
|
|
355
|
+
query: undefined,
|
|
356
|
+
init
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
async getContact(id, init) {
|
|
360
|
+
return this.request("GET", `/v1/contacts/${encodeURIComponent(String(id))}`, {
|
|
361
|
+
body: undefined,
|
|
362
|
+
query: undefined,
|
|
363
|
+
init
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
async deleteContact(id, init) {
|
|
367
|
+
return this.request("DELETE", `/v1/contacts/${encodeURIComponent(String(id))}`, {
|
|
368
|
+
body: undefined,
|
|
369
|
+
query: undefined,
|
|
370
|
+
init
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
async updateContact(id, body, init) {
|
|
374
|
+
return this.request("PATCH", `/v1/contacts/${encodeURIComponent(String(id))}`, {
|
|
375
|
+
body,
|
|
376
|
+
query: undefined,
|
|
377
|
+
init
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
async listContactProjectMemberships(projectId, query, init) {
|
|
381
|
+
return this.request("GET", `/v1/projects/${encodeURIComponent(String(projectId))}/contact-memberships`, {
|
|
382
|
+
body: undefined,
|
|
383
|
+
query,
|
|
384
|
+
init
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
async readContactProjectMembership(projectId, contactId, init) {
|
|
388
|
+
return this.request("GET", `/v1/projects/${encodeURIComponent(String(projectId))}/contact-memberships/${encodeURIComponent(String(contactId))}`, {
|
|
389
|
+
body: undefined,
|
|
390
|
+
query: undefined,
|
|
391
|
+
init
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
async attachContactProjectMembership(projectId, contactId, body, init) {
|
|
395
|
+
return this.request("POST", `/v1/projects/${encodeURIComponent(String(projectId))}/contact-memberships/${encodeURIComponent(String(contactId))}/attach`, {
|
|
396
|
+
body,
|
|
397
|
+
query: undefined,
|
|
398
|
+
init
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
async detachContactProjectMembership(projectId, contactId, body, init) {
|
|
402
|
+
return this.request("POST", `/v1/projects/${encodeURIComponent(String(projectId))}/contact-memberships/${encodeURIComponent(String(contactId))}/detach`, {
|
|
403
|
+
body,
|
|
404
|
+
query: undefined,
|
|
405
|
+
init
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
async listContactIdsByProject(projectId, init) {
|
|
409
|
+
return this.request("GET", `/v1/projects/${encodeURIComponent(String(projectId))}/contacts`, {
|
|
410
|
+
body: undefined,
|
|
411
|
+
query: undefined,
|
|
412
|
+
init
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
async getStats(init) {
|
|
416
|
+
return this.request("GET", `/v1/stats`, {
|
|
417
|
+
body: undefined,
|
|
418
|
+
query: undefined,
|
|
419
|
+
init
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async listTags(query, init) {
|
|
423
|
+
return this.request("GET", `/v1/tags`, {
|
|
424
|
+
body: undefined,
|
|
425
|
+
query,
|
|
426
|
+
init
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
async createTag(body, init) {
|
|
430
|
+
return this.request("POST", `/v1/tags`, {
|
|
431
|
+
body,
|
|
432
|
+
query: undefined,
|
|
433
|
+
init
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
async getTag(id, init) {
|
|
437
|
+
return this.request("GET", `/v1/tags/${encodeURIComponent(String(id))}`, {
|
|
438
|
+
body: undefined,
|
|
439
|
+
query: undefined,
|
|
440
|
+
init
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
async deleteTag(id, init) {
|
|
444
|
+
return this.request("DELETE", `/v1/tags/${encodeURIComponent(String(id))}`, {
|
|
445
|
+
body: undefined,
|
|
446
|
+
query: undefined,
|
|
447
|
+
init
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
async updateTag(id, body, init) {
|
|
451
|
+
return this.request("PATCH", `/v1/tags/${encodeURIComponent(String(id))}`, {
|
|
452
|
+
body,
|
|
453
|
+
query: undefined,
|
|
454
|
+
init
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/sdk/index.ts
|
|
460
|
+
function validateBaseUrl(raw) {
|
|
461
|
+
let url;
|
|
462
|
+
try {
|
|
463
|
+
url = new URL(raw);
|
|
464
|
+
} catch {
|
|
465
|
+
throw new Error("ContactsV1Client baseUrl must be an absolute HTTPS URL.");
|
|
466
|
+
}
|
|
467
|
+
if (url.protocol !== "https:")
|
|
468
|
+
throw new Error("ContactsV1Client baseUrl must use HTTPS.");
|
|
469
|
+
if (url.username || url.password)
|
|
470
|
+
throw new Error("ContactsV1Client baseUrl must not contain credentials.");
|
|
471
|
+
if (url.search || url.hash)
|
|
472
|
+
throw new Error("ContactsV1Client baseUrl must not contain a query or fragment.");
|
|
473
|
+
url.pathname = url.pathname.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
474
|
+
return url.toString().replace(/\/+$/, "");
|
|
475
|
+
}
|
|
476
|
+
function validateApiKey(apiKey) {
|
|
477
|
+
const key = (apiKey ?? "").trim();
|
|
478
|
+
if (!key)
|
|
479
|
+
throw new Error("ContactsV1Client requires an API key.");
|
|
480
|
+
if (/[^\t\x20-\x7e]/.test(key)) {
|
|
481
|
+
throw new Error("ContactsV1Client API key contains bytes that are invalid in an HTTP header.");
|
|
482
|
+
}
|
|
483
|
+
return key;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
class ContactsV1Client2 extends ContactsV1Client {
|
|
487
|
+
constructor(options) {
|
|
488
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
489
|
+
super({
|
|
490
|
+
...options,
|
|
491
|
+
baseUrl: validateBaseUrl(options.baseUrl),
|
|
492
|
+
apiKey: validateApiKey(options.apiKey),
|
|
493
|
+
fetch: (input, init) => fetchImpl(input, { ...init, redirect: "manual" })
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
var CONTACTS_APP_NAME = "contacts";
|
|
498
|
+
function contactsSdkAuthorityPinMessage() {
|
|
499
|
+
return "an explicit baseUrl requires an explicit apiKey. The SDK never attaches a credential that " + "resolved for a different authority: pass `apiKey` explicitly, or omit `baseUrl` and let the " + "@hasna/contracts chain resolve both halves together.";
|
|
500
|
+
}
|
|
501
|
+
var SDK_HOSTED_ONLY = "The contacts SDK is hosted-only and never falls back to local data.";
|
|
502
|
+
function resolveSdkCredential(env, chainOptions) {
|
|
503
|
+
const credential = resolveCredential2(CONTACTS_APP_NAME, env, chainOptions);
|
|
504
|
+
if (!credential) {
|
|
505
|
+
const diagnosis = resolveContactsClientTransport(CONTACTS_APP_NAME, env, chainOptions);
|
|
506
|
+
throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", `${diagnosis.issue ?? "No contacts credential resolved."} ${SDK_HOSTED_ONLY}`);
|
|
507
|
+
}
|
|
508
|
+
if (credential.tier === "pointer") {
|
|
509
|
+
throw new ContactsClientConfigurationError("CONTACTS_CREDENTIAL_POINTER_UNSUPPORTED", `The contacts SDK resolves credentials synchronously and cannot complete the secrets-vault pointer ` + `${credential.source} per request. Use a literal tier instead: an explicit apiKey, the Keychain item ` + `hasna.credentials.${CONTACTS_APP_NAME}.api-key, ~/.hasna/${CONTACTS_APP_NAME}/config/credentials, ` + `or HASNA_CONTACTS_API_KEY.`);
|
|
510
|
+
}
|
|
511
|
+
return credential.apiKey;
|
|
512
|
+
}
|
|
513
|
+
function createContactsClient(options = {}) {
|
|
514
|
+
const { baseUrl, apiKey, env, profile, keychain, ...clientOptions } = options;
|
|
515
|
+
if (baseUrl !== undefined) {
|
|
516
|
+
if (!apiKey)
|
|
517
|
+
throw new ContactsClientConfigurationError("CONTACTS_CREDENTIAL_PINNED", contactsSdkAuthorityPinMessage());
|
|
518
|
+
return new ContactsV1Client2({ ...clientOptions, baseUrl, apiKey });
|
|
519
|
+
}
|
|
520
|
+
const envObject = env ?? (typeof process !== "undefined" ? process.env : {});
|
|
521
|
+
const requested = {
|
|
522
|
+
...apiKey !== undefined ? { apiKey } : {},
|
|
523
|
+
...profile !== undefined ? { profile } : {},
|
|
524
|
+
...keychain !== undefined ? { keychain } : {}
|
|
525
|
+
};
|
|
526
|
+
const chainOptions = contactsResolverCredentials(envObject, requested);
|
|
527
|
+
const initialKey = resolveSdkCredential(envObject, chainOptions);
|
|
528
|
+
const resolution = resolveContactsClientTransport(CONTACTS_APP_NAME, envObject, { ...chainOptions, apiKey: initialKey });
|
|
529
|
+
if (!resolution.configured || !resolution.baseUrl) {
|
|
530
|
+
throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", `${resolution.issue ?? "The contacts API client is not configured."} ${SDK_HOSTED_ONLY}`);
|
|
531
|
+
}
|
|
532
|
+
const pinnedBaseUrl = resolution.baseUrl;
|
|
533
|
+
const baseFetch = clientOptions.fetch ?? globalThis.fetch;
|
|
534
|
+
const chainFetch = async (input, init) => {
|
|
535
|
+
const snapshot = { ...envObject };
|
|
536
|
+
const freshKey = resolveSdkCredential(snapshot, chainOptions);
|
|
537
|
+
const current = resolveContactsClientTransport(CONTACTS_APP_NAME, snapshot, { ...chainOptions, apiKey: freshKey });
|
|
538
|
+
if (!current.configured || current.baseUrl !== pinnedBaseUrl) {
|
|
539
|
+
throw new ContactsClientConfigurationError("CONTACTS_AUTHORITY_CHANGED", "Client authority changed or disappeared; construct a new client before sending data.");
|
|
540
|
+
}
|
|
541
|
+
const headers = new Headers(init?.headers);
|
|
542
|
+
headers.set("x-api-key", freshKey);
|
|
543
|
+
return baseFetch(input, { ...init, headers });
|
|
544
|
+
};
|
|
545
|
+
return new ContactsV1Client2({ ...clientOptions, baseUrl: pinnedBaseUrl, apiKey: initialKey, fetch: chainFetch });
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/browser/identity.ts
|
|
549
|
+
var EXTENSION_ID = "ceegpmbcoiomonopbhpifdalooccfgjj";
|
|
550
|
+
|
|
551
|
+
// src/browser/protocol.ts
|
|
552
|
+
class BrowserError extends Error {
|
|
553
|
+
code;
|
|
554
|
+
constructor(code) {
|
|
555
|
+
super(code);
|
|
556
|
+
this.code = code;
|
|
557
|
+
this.name = "BrowserError";
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function safeError(error) {
|
|
561
|
+
return error instanceof BrowserError ? error.code : "CONTACTS_UNAVAILABLE";
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
class NativeProtocol {
|
|
565
|
+
handlers;
|
|
566
|
+
buffer = Buffer.alloc(0);
|
|
567
|
+
queued = 0;
|
|
568
|
+
chain = Promise.resolve();
|
|
569
|
+
constructor(handlers) {
|
|
570
|
+
this.handlers = handlers;
|
|
571
|
+
}
|
|
572
|
+
accept(chunk) {
|
|
573
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
574
|
+
if (this.buffer.length > 1048576)
|
|
575
|
+
return this.handlers.fail();
|
|
576
|
+
while (this.buffer.length >= 4) {
|
|
577
|
+
const length = this.buffer.readUInt32LE(0);
|
|
578
|
+
if (!length || length > 65536)
|
|
579
|
+
return this.handlers.fail();
|
|
580
|
+
if (this.buffer.length < 4 + length)
|
|
581
|
+
break;
|
|
582
|
+
let message;
|
|
583
|
+
try {
|
|
584
|
+
message = JSON.parse(this.buffer.subarray(4, 4 + length).toString());
|
|
585
|
+
} catch {
|
|
586
|
+
return this.handlers.fail();
|
|
587
|
+
}
|
|
588
|
+
this.buffer = this.buffer.subarray(4 + length);
|
|
589
|
+
if (!message || typeof message.id !== "string" || !/^[0-9a-f-]{36}$/.test(message.id))
|
|
590
|
+
return this.handlers.fail();
|
|
591
|
+
if (++this.queued > 32)
|
|
592
|
+
return this.handlers.fail();
|
|
593
|
+
this.chain = this.chain.then(async () => {
|
|
594
|
+
try {
|
|
595
|
+
const result = await this.handlers.handle(message);
|
|
596
|
+
this.handlers.send({ id: message.id, ok: true, result });
|
|
597
|
+
} catch (e) {
|
|
598
|
+
this.handlers.send({ id: message.id, ok: false, error: safeError(e) });
|
|
599
|
+
} finally {
|
|
600
|
+
this.queued--;
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
async idle() {
|
|
606
|
+
await this.chain;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function encodeFrame(value) {
|
|
610
|
+
const body = Buffer.from(JSON.stringify(value));
|
|
611
|
+
if (body.length > 512000)
|
|
612
|
+
throw new BrowserError("RESPONSE_TOO_LARGE");
|
|
613
|
+
const header = Buffer.alloc(4);
|
|
614
|
+
header.writeUInt32LE(body.length);
|
|
615
|
+
return Buffer.concat([header, body]);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// src/browser/values.ts
|
|
619
|
+
function rec(value) {
|
|
620
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
621
|
+
}
|
|
622
|
+
function text(value, max = 200) {
|
|
623
|
+
return typeof value === "string" && value.length <= max ? value.trim() : "";
|
|
624
|
+
}
|
|
625
|
+
function list(value) {
|
|
626
|
+
return Array.isArray(value) ? value.map(rec).filter((x) => x !== null) : [];
|
|
627
|
+
}
|
|
628
|
+
function primary(items) {
|
|
629
|
+
return items.find((x) => x.is_primary === true) ?? items[0] ?? null;
|
|
630
|
+
}
|
|
631
|
+
function contactId(value) {
|
|
632
|
+
if (typeof value !== "string" || !/^[a-zA-Z0-9_-]{1,128}$/.test(value))
|
|
633
|
+
throw new BrowserError("CONTACT_NOT_FOUND");
|
|
634
|
+
return value;
|
|
635
|
+
}
|
|
636
|
+
function searchQuery(value) {
|
|
637
|
+
if (value === undefined || value === null)
|
|
638
|
+
return "";
|
|
639
|
+
if (typeof value !== "string")
|
|
640
|
+
throw new BrowserError("INVALID_QUERY");
|
|
641
|
+
return value.trim().slice(0, 200);
|
|
642
|
+
}
|
|
643
|
+
var REGION_NAMES = (() => {
|
|
644
|
+
try {
|
|
645
|
+
return new Intl.DisplayNames(["en"], { type: "region" });
|
|
646
|
+
} catch {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
})();
|
|
650
|
+
var REGION_CODES = "AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS YE YT ZA ZM ZW".split(" ");
|
|
651
|
+
var ALIAS_NAMES = {
|
|
652
|
+
usa: "US",
|
|
653
|
+
unitedstates: "US",
|
|
654
|
+
unitedstatesofamerica: "US",
|
|
655
|
+
uk: "GB",
|
|
656
|
+
unitedkingdom: "GB",
|
|
657
|
+
greatbritain: "GB",
|
|
658
|
+
england: "GB",
|
|
659
|
+
romania: "RO",
|
|
660
|
+
germany: "DE",
|
|
661
|
+
france: "FR",
|
|
662
|
+
spain: "ES",
|
|
663
|
+
italy: "IT",
|
|
664
|
+
netherlands: "NL",
|
|
665
|
+
thenetherlands: "NL"
|
|
666
|
+
};
|
|
667
|
+
function countryCode(value) {
|
|
668
|
+
const raw = value.trim();
|
|
669
|
+
if (!raw)
|
|
670
|
+
return "";
|
|
671
|
+
if (/^[A-Za-z]{2}$/.test(raw))
|
|
672
|
+
return raw.toUpperCase();
|
|
673
|
+
const norm = raw.toLowerCase().replace(/[^a-z]/g, "");
|
|
674
|
+
if (ALIAS_NAMES[norm])
|
|
675
|
+
return ALIAS_NAMES[norm];
|
|
676
|
+
if (REGION_NAMES) {
|
|
677
|
+
for (const code of REGION_CODES) {
|
|
678
|
+
let name;
|
|
679
|
+
try {
|
|
680
|
+
name = REGION_NAMES.of(code);
|
|
681
|
+
} catch {
|
|
682
|
+
name = undefined;
|
|
683
|
+
}
|
|
684
|
+
if (name && name.toLowerCase().replace(/[^a-z]/g, "") === norm)
|
|
685
|
+
return code;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return raw;
|
|
689
|
+
}
|
|
690
|
+
function contactSummary(contact) {
|
|
691
|
+
const c = rec(contact);
|
|
692
|
+
if (!c || typeof c.id !== "string")
|
|
693
|
+
return null;
|
|
694
|
+
const email = primary(list(c.emails));
|
|
695
|
+
const company = rec(c.company);
|
|
696
|
+
const name = text(c.display_name) || [text(c.first_name), text(c.last_name)].filter(Boolean).join(" ") || text(c.nickname) || "Unnamed contact";
|
|
697
|
+
return { id: c.id, name, company: text(company?.name, 120), email: text(email?.address, 254) };
|
|
698
|
+
}
|
|
699
|
+
function contactValues(contact) {
|
|
700
|
+
const c = rec(contact);
|
|
701
|
+
if (!c)
|
|
702
|
+
throw new BrowserError("CONTACT_NOT_FOUND");
|
|
703
|
+
const out = {};
|
|
704
|
+
const set = (key, value, max = 200) => {
|
|
705
|
+
const v = text(value, max);
|
|
706
|
+
if (v)
|
|
707
|
+
out[key] = v;
|
|
708
|
+
};
|
|
709
|
+
const first = text(c.first_name);
|
|
710
|
+
const last = text(c.last_name);
|
|
711
|
+
set("given-name", first);
|
|
712
|
+
set("family-name", last);
|
|
713
|
+
set("name", text(c.display_name) || [first, last].filter(Boolean).join(" "));
|
|
714
|
+
set("nickname", c.nickname);
|
|
715
|
+
set("organization", rec(c.company)?.name, 120);
|
|
716
|
+
set("organization-title", c.job_title, 120);
|
|
717
|
+
set("url", c.website, 2048);
|
|
718
|
+
const email = primary(list(c.emails));
|
|
719
|
+
set("email", email?.address, 254);
|
|
720
|
+
const phone = primary(list(c.phones));
|
|
721
|
+
if (phone) {
|
|
722
|
+
const number = text(phone.number, 40);
|
|
723
|
+
const code = text(phone.country_code, 6);
|
|
724
|
+
if (number)
|
|
725
|
+
out.tel = code && !number.startsWith("+") ? `${code.startsWith("+") ? code : `+${code}`} ${number}`.trim() : number;
|
|
726
|
+
}
|
|
727
|
+
const address = primary(list(c.addresses));
|
|
728
|
+
if (address) {
|
|
729
|
+
const street = text(address.street, 300);
|
|
730
|
+
if (street) {
|
|
731
|
+
const lines = street.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
732
|
+
out["street-address"] = lines.join(`
|
|
733
|
+
`);
|
|
734
|
+
out["address-line1"] = lines[0] ?? street;
|
|
735
|
+
if (lines[1])
|
|
736
|
+
out["address-line2"] = lines.slice(1).join(", ");
|
|
737
|
+
}
|
|
738
|
+
set("address-level2", address.city, 120);
|
|
739
|
+
set("address-level1", address.state, 120);
|
|
740
|
+
set("postal-code", address.zip, 40);
|
|
741
|
+
const country = text(address.country, 120);
|
|
742
|
+
if (country)
|
|
743
|
+
out.country = countryCode(country);
|
|
744
|
+
}
|
|
745
|
+
const birthday = text(c.birthday, 40);
|
|
746
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(birthday))
|
|
747
|
+
out.bday = birthday.slice(0, 10);
|
|
748
|
+
return out;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/browser/native.ts
|
|
752
|
+
var version = (() => {
|
|
753
|
+
try {
|
|
754
|
+
return createRequire(import.meta.url)("../../package.json").version;
|
|
755
|
+
} catch {
|
|
756
|
+
return "unknown";
|
|
757
|
+
}
|
|
758
|
+
})();
|
|
759
|
+
if (process.argv[2] !== `chrome-extension://${EXTENSION_ID}/` || process.stdout.isTTY)
|
|
760
|
+
process.exit(1);
|
|
761
|
+
var client = null;
|
|
762
|
+
var configured = null;
|
|
763
|
+
function api() {
|
|
764
|
+
if (client)
|
|
765
|
+
return client;
|
|
766
|
+
if (configured)
|
|
767
|
+
throw new BrowserError(configured);
|
|
768
|
+
try {
|
|
769
|
+
client = createContactsClient();
|
|
770
|
+
return client;
|
|
771
|
+
} catch (error) {
|
|
772
|
+
configured = error instanceof ContactsClientConfigurationError ? "CONTACTS_NOT_CONFIGURED" : "CONTACTS_UNAVAILABLE";
|
|
773
|
+
throw new BrowserError(configured);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
var browserId;
|
|
777
|
+
function send(value) {
|
|
778
|
+
process.stdout.write(encodeFrame(value));
|
|
779
|
+
}
|
|
780
|
+
process.stdin.on("end", () => process.exit(0));
|
|
781
|
+
process.stdin.on("error", () => process.exit(1));
|
|
782
|
+
process.stdout.on("error", () => process.exit(1));
|
|
783
|
+
async function handle(message) {
|
|
784
|
+
if (message.op === "hello") {
|
|
785
|
+
if (typeof message.browserId !== "string" || !/^[0-9a-f-]{36}$/.test(message.browserId))
|
|
786
|
+
throw new BrowserError("INVALID_BROWSER_ID");
|
|
787
|
+
browserId = message.browserId;
|
|
788
|
+
return { browserId, version };
|
|
789
|
+
}
|
|
790
|
+
if (!browserId)
|
|
791
|
+
throw new BrowserError("HELLO_REQUIRED");
|
|
792
|
+
if (message.op === "status")
|
|
793
|
+
return { browserId, version, configured: configured === null };
|
|
794
|
+
if (message.op === "search") {
|
|
795
|
+
const query = searchQuery(message.query);
|
|
796
|
+
const response = query ? await api().listContacts({ q: query, limit: 20 }) : await api().listContacts({ limit: 20, ...{ order_by: "updated_at", order_dir: "desc" } });
|
|
797
|
+
const contacts = Array.isArray(response?.contacts) ? response.contacts : [];
|
|
798
|
+
return contacts.map(contactSummary).filter((x) => x !== null);
|
|
799
|
+
}
|
|
800
|
+
if (message.op === "resolve") {
|
|
801
|
+
const id = contactId(message.contactId);
|
|
802
|
+
let response = null;
|
|
803
|
+
try {
|
|
804
|
+
response = await api().getContact(id);
|
|
805
|
+
} catch (error) {
|
|
806
|
+
if (error instanceof BrowserError)
|
|
807
|
+
throw error;
|
|
808
|
+
if (error?.status === 404)
|
|
809
|
+
throw new BrowserError("CONTACT_NOT_FOUND");
|
|
810
|
+
throw new BrowserError("CONTACTS_UNAVAILABLE");
|
|
811
|
+
}
|
|
812
|
+
const contact = response?.contact ?? response;
|
|
813
|
+
if (!contact || contact.id !== id)
|
|
814
|
+
throw new BrowserError("CONTACT_NOT_FOUND");
|
|
815
|
+
return contactValues(contact);
|
|
816
|
+
}
|
|
817
|
+
throw new BrowserError("UNKNOWN_OPERATION");
|
|
818
|
+
}
|
|
819
|
+
var protocol = new NativeProtocol({ handle, send, fail: () => process.exit(1) });
|
|
820
|
+
process.stdin.on("data", (chunk) => {
|
|
821
|
+
protocol.accept(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
822
|
+
});
|