@aaronsb/google-workspace-mcp 4.2.1 → 4.3.2
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 +6 -5
- package/build/accounts/auth.d.ts +18 -2
- package/build/accounts/auth.js +31 -7
- package/build/accounts/auth.js.map +1 -1
- package/build/accounts/credentials.d.ts +20 -0
- package/build/accounts/credentials.js.map +1 -1
- package/build/accounts/oauth.d.ts +51 -1
- package/build/accounts/oauth.js +69 -5
- package/build/accounts/oauth.js.map +1 -1
- package/build/accounts/registry.d.ts +2 -1
- package/build/accounts/registry.js +2 -2
- package/build/accounts/registry.js.map +1 -1
- package/build/factory/manifest/contacts.yaml +247 -0
- package/build/factory/manifest/meet.yaml +78 -0
- package/build/factory/patches.js +2 -0
- package/build/factory/patches.js.map +1 -1
- package/build/factory/safety.js +4 -0
- package/build/factory/safety.js.map +1 -1
- package/build/google/descriptor.d.ts +8 -0
- package/build/google/descriptor.js +9 -2
- package/build/google/descriptor.js.map +1 -1
- package/build/google/descriptor.json +1883 -0
- package/build/google/methods.d.ts +4 -1
- package/build/server/handler.js +16 -1
- package/build/server/handler.js.map +1 -1
- package/build/server/handlers/accounts.js +100 -6
- package/build/server/handlers/accounts.js.map +1 -1
- package/build/server/queue.d.ts +1 -1
- package/build/server/queue.js +16 -1
- package/build/server/queue.js.map +1 -1
- package/build/server/tools.js +31 -1
- package/build/server/tools.js.map +1 -1
- package/build/services/contacts/patch.d.ts +31 -0
- package/build/services/contacts/patch.js +496 -0
- package/build/services/contacts/patch.js.map +1 -0
- package/build/services/meet/patch.js +202 -0
- package/build/services/meet/patch.js.map +1 -1
- package/build/version.d.ts +1 -1
- package/build/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contacts patch — domain-specific hooks for the People API.
|
|
3
|
+
*
|
|
4
|
+
* The People API returns a Person as PARALLEL ARRAYS. A name is not a string, it is
|
|
5
|
+
* `names: [{ displayName, metadata: { primary } }, ...]`; an email is not a string, it
|
|
6
|
+
* is `emailAddresses: [...]`. Every field works this way, one array per field, each
|
|
7
|
+
* entry carrying its own metadata, and the entry that counts is flagged rather than
|
|
8
|
+
* ordered. Handed to the generic formatter this renders as a person with no name and
|
|
9
|
+
* no address — the same failure mode as a Docs read that returned metadata and no text.
|
|
10
|
+
*
|
|
11
|
+
* The second reshaping job is the envelope. Six read operations, four different keys
|
|
12
|
+
* for "the people you asked for":
|
|
13
|
+
*
|
|
14
|
+
* people.connections.list -> { connections: [Person] }
|
|
15
|
+
* otherContacts.list -> { otherContacts: [Person] }
|
|
16
|
+
* people.*DirectoryPeople -> { people: [Person] }
|
|
17
|
+
* *.search -> { results: [{ person: Person }] }
|
|
18
|
+
*
|
|
19
|
+
* The agent picks an operation; it should not then have to know which of four shapes
|
|
20
|
+
* came back.
|
|
21
|
+
*/
|
|
22
|
+
import { call } from '../../google/client.js';
|
|
23
|
+
function asRec(value) {
|
|
24
|
+
return value && typeof value === 'object' ? value : {};
|
|
25
|
+
}
|
|
26
|
+
function asArray(value) {
|
|
27
|
+
return Array.isArray(value) ? value.map(asRec) : [];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The entries of one Person field, primary first.
|
|
31
|
+
*
|
|
32
|
+
* Google flags the entry that counts with `metadata.primary` rather than putting it
|
|
33
|
+
* first, so taking `[0]` picks an arbitrary one. On an account with a work address and
|
|
34
|
+
* a personal one that is a coin flip over which address a reply goes to.
|
|
35
|
+
*/
|
|
36
|
+
function entries(person, field) {
|
|
37
|
+
const all = asArray(person[field]);
|
|
38
|
+
const primary = all.filter((e) => asRec(e.metadata).primary === true);
|
|
39
|
+
const rest = all.filter((e) => asRec(e.metadata).primary !== true);
|
|
40
|
+
return [...primary, ...rest];
|
|
41
|
+
}
|
|
42
|
+
/** The primary value of a field, or '' when the field is absent. */
|
|
43
|
+
function primaryValue(person, field, key = 'value') {
|
|
44
|
+
const first = entries(person, field)[0];
|
|
45
|
+
const value = first?.[key];
|
|
46
|
+
return typeof value === 'string' ? value : '';
|
|
47
|
+
}
|
|
48
|
+
/** Every value of a field, each labelled with its type ("work", "home", ...). */
|
|
49
|
+
function labelledValues(person, field, key = 'value') {
|
|
50
|
+
return entries(person, field)
|
|
51
|
+
.map((e) => {
|
|
52
|
+
const value = e[key];
|
|
53
|
+
if (typeof value !== 'string' || !value)
|
|
54
|
+
return '';
|
|
55
|
+
const label = typeof e.formattedType === 'string' ? e.formattedType : '';
|
|
56
|
+
return label ? `${value} (${label.toLowerCase()})` : value;
|
|
57
|
+
})
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join(', ');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A display name, falling back through what the API actually returns.
|
|
63
|
+
*
|
|
64
|
+
* `displayName` is absent on a person who has only an email address — which is the
|
|
65
|
+
* common case for `listOther`, where the whole point is addresses nobody ever saved.
|
|
66
|
+
* Rendering those as blank rows loses the only fact the operation had to offer.
|
|
67
|
+
*/
|
|
68
|
+
function displayName(person) {
|
|
69
|
+
const name = primaryValue(person, 'names', 'displayName');
|
|
70
|
+
if (name)
|
|
71
|
+
return name;
|
|
72
|
+
const email = primaryValue(person, 'emailAddresses');
|
|
73
|
+
if (email)
|
|
74
|
+
return email;
|
|
75
|
+
const phone = primaryValue(person, 'phoneNumbers');
|
|
76
|
+
return phone || '(no name)';
|
|
77
|
+
}
|
|
78
|
+
/** "Acme — Field Engineer", or whichever half exists. */
|
|
79
|
+
function organization(person) {
|
|
80
|
+
const org = entries(person, 'organizations')[0];
|
|
81
|
+
if (!org)
|
|
82
|
+
return '';
|
|
83
|
+
const name = typeof org.name === 'string' ? org.name : '';
|
|
84
|
+
const title = typeof org.title === 'string' ? org.title : '';
|
|
85
|
+
return [name, title].filter(Boolean).join(' — ');
|
|
86
|
+
}
|
|
87
|
+
function personId(person) {
|
|
88
|
+
return typeof person.resourceName === 'string' ? person.resourceName : '';
|
|
89
|
+
}
|
|
90
|
+
// --- Envelope ---
|
|
91
|
+
/** The people in a response, whichever of the four keys this operation used. */
|
|
92
|
+
function peopleFrom(data) {
|
|
93
|
+
const raw = asRec(data);
|
|
94
|
+
for (const key of ['connections', 'otherContacts', 'people']) {
|
|
95
|
+
if (Array.isArray(raw[key]))
|
|
96
|
+
return asArray(raw[key]);
|
|
97
|
+
}
|
|
98
|
+
// Search responses wrap each hit: { results: [{ person: {...} }] }
|
|
99
|
+
if (Array.isArray(raw.results))
|
|
100
|
+
return asArray(raw.results).map((r) => asRec(r.person));
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
/** The total Google reported, under whichever name it used for this operation. */
|
|
104
|
+
function totalFrom(data) {
|
|
105
|
+
const raw = asRec(data);
|
|
106
|
+
for (const key of ['totalPeople', 'totalItems', 'totalSize']) {
|
|
107
|
+
if (typeof raw[key] === 'number')
|
|
108
|
+
return raw[key];
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
// --- Formatters ---
|
|
113
|
+
function formatPersonList(data, ctx) {
|
|
114
|
+
const people = peopleFrom(data);
|
|
115
|
+
const raw = asRec(data);
|
|
116
|
+
const nextPageToken = typeof raw.nextPageToken === 'string' ? raw.nextPageToken : '';
|
|
117
|
+
if (people.length === 0) {
|
|
118
|
+
const hint = ctx.operation.startsWith('search')
|
|
119
|
+
? ` No match for "${String(ctx.params.query ?? '')}". Matching is by prefix, so a fragment from the middle of a name finds nothing.`
|
|
120
|
+
: '';
|
|
121
|
+
return { text: `No contacts found.${hint}`, refs: { count: 0 } };
|
|
122
|
+
}
|
|
123
|
+
const total = totalFrom(data);
|
|
124
|
+
const heading = total !== undefined && total > people.length
|
|
125
|
+
? `## Contacts (${people.length} of ${total})`
|
|
126
|
+
: `## Contacts (${people.length})`;
|
|
127
|
+
const rows = people.map((p) => [
|
|
128
|
+
personId(p),
|
|
129
|
+
displayName(p),
|
|
130
|
+
primaryValue(p, 'emailAddresses'),
|
|
131
|
+
primaryValue(p, 'phoneNumbers'),
|
|
132
|
+
organization(p),
|
|
133
|
+
].filter(Boolean).join(' | '));
|
|
134
|
+
const parts = [heading, '', ...rows];
|
|
135
|
+
// Saying nothing here would present a truncated list as the whole list — the caller
|
|
136
|
+
// has no other way to learn the page ended early.
|
|
137
|
+
if (nextPageToken) {
|
|
138
|
+
parts.push('', `More to come. Call \`${ctx.operation}\` again with pageToken: ${nextPageToken}`);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
text: parts.join('\n'),
|
|
142
|
+
refs: {
|
|
143
|
+
count: people.length,
|
|
144
|
+
ids: people.map(personId),
|
|
145
|
+
...(nextPageToken ? { nextPageToken } : {}),
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function formatPersonDetail(data) {
|
|
150
|
+
const person = asRec(data);
|
|
151
|
+
const id = personId(person);
|
|
152
|
+
const parts = [`## ${displayName(person)}`, '', id];
|
|
153
|
+
const field = (label, value) => {
|
|
154
|
+
if (value)
|
|
155
|
+
parts.push(`**${label}:** ${value}`);
|
|
156
|
+
};
|
|
157
|
+
parts.push('');
|
|
158
|
+
field('Email', labelledValues(person, 'emailAddresses'));
|
|
159
|
+
field('Phone', labelledValues(person, 'phoneNumbers'));
|
|
160
|
+
field('Organization', organization(person));
|
|
161
|
+
field('Address', labelledValues(person, 'addresses', 'formattedValue'));
|
|
162
|
+
field('Nickname', primaryValue(person, 'nicknames'));
|
|
163
|
+
field('Links', entries(person, 'urls').map((u) => String(u.value ?? '')).filter(Boolean).join(', '));
|
|
164
|
+
field('Relations', entries(person, 'relations')
|
|
165
|
+
.map((r) => {
|
|
166
|
+
const who = String(r.person ?? '');
|
|
167
|
+
const how = String(r.formattedType ?? '');
|
|
168
|
+
return who ? (how ? `${who} (${how.toLowerCase()})` : who) : '';
|
|
169
|
+
})
|
|
170
|
+
.filter(Boolean)
|
|
171
|
+
.join(', '));
|
|
172
|
+
const birthday = entries(person, 'birthdays')[0];
|
|
173
|
+
if (birthday) {
|
|
174
|
+
const date = asRec(birthday.date);
|
|
175
|
+
const text = typeof birthday.text === 'string' ? birthday.text : '';
|
|
176
|
+
// A birthday with no year is normal — Google stores the day alone when that is all
|
|
177
|
+
// it was given, so a year-less date is data, not a partial record to discard.
|
|
178
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
179
|
+
const composed = date.month && date.day
|
|
180
|
+
? [date.year, pad(date.month), pad(date.day)].filter(Boolean).join('-')
|
|
181
|
+
: text;
|
|
182
|
+
field('Birthday', String(composed ?? ''));
|
|
183
|
+
}
|
|
184
|
+
const notes = primaryValue(person, 'biographies');
|
|
185
|
+
if (notes)
|
|
186
|
+
parts.push('', '### Notes', '', notes);
|
|
187
|
+
return { text: parts.join('\n'), refs: { id, contactId: id } };
|
|
188
|
+
}
|
|
189
|
+
// --- Request shaping ---
|
|
190
|
+
/**
|
|
191
|
+
* Accept a person id with or without its `people/` prefix.
|
|
192
|
+
*
|
|
193
|
+
* Every operation prints ids in full, so the prefix is normally present; a caller that
|
|
194
|
+
* copied only the opaque half gets the record rather than a 404 that reads like the
|
|
195
|
+
* person does not exist.
|
|
196
|
+
*/
|
|
197
|
+
function normalizeContactId(params) {
|
|
198
|
+
const raw = params.resourceName;
|
|
199
|
+
if (typeof raw !== 'string')
|
|
200
|
+
return params;
|
|
201
|
+
const trimmed = raw.trim();
|
|
202
|
+
return { ...params, resourceName: trimmed.startsWith('people/') ? trimmed : `people/${trimmed}` };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Google's sort values are LAST_MODIFIED_DESCENDING and friends. The manifest advertises
|
|
206
|
+
* `firstName` / `lastName` / `lastModified` and this maps them, so the agent does not
|
|
207
|
+
* carry an API constant it can only get subtly wrong.
|
|
208
|
+
*/
|
|
209
|
+
export const SORT_ORDERS = {
|
|
210
|
+
firstName: 'FIRST_NAME_ASCENDING',
|
|
211
|
+
lastName: 'LAST_NAME_ASCENDING',
|
|
212
|
+
lastModified: 'LAST_MODIFIED_DESCENDING',
|
|
213
|
+
};
|
|
214
|
+
function mapSortOrder(params) {
|
|
215
|
+
const raw = params.sortOrder;
|
|
216
|
+
if (typeof raw !== 'string')
|
|
217
|
+
return params;
|
|
218
|
+
const mapped = SORT_ORDERS[raw];
|
|
219
|
+
if (!mapped) {
|
|
220
|
+
throw new Error(`sortOrder must be one of ${Object.keys(SORT_ORDERS).join(', ')}, got '${raw}'.`);
|
|
221
|
+
}
|
|
222
|
+
return { ...params, sortOrder: mapped };
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Turn the flat parameters the schema advertises into the Person body Google wants.
|
|
226
|
+
*
|
|
227
|
+
* This is the write half of the same shape problem the formatters solve for reads. A
|
|
228
|
+
* contact's name is `names: [{ givenName, familyName }]`, and anything this server sends
|
|
229
|
+
* that Google does not recognise as a query parameter lands in the request body verbatim
|
|
230
|
+
* — so a flat `name` is accepted, ignored, and returns 200 with a nameless contact. The
|
|
231
|
+
* failure has no error in it anywhere.
|
|
232
|
+
*/
|
|
233
|
+
const WRITABLE = ['name', 'contactEmail', 'phone', 'company', 'jobTitle', 'notes'];
|
|
234
|
+
/** Which Person field each flat parameter writes to. company and jobTitle share one. */
|
|
235
|
+
const PERSON_FIELD = {
|
|
236
|
+
name: 'names',
|
|
237
|
+
contactEmail: 'emailAddresses',
|
|
238
|
+
phone: 'phoneNumbers',
|
|
239
|
+
company: 'organizations',
|
|
240
|
+
jobTitle: 'organizations',
|
|
241
|
+
notes: 'biographies',
|
|
242
|
+
};
|
|
243
|
+
function trimmed(value) {
|
|
244
|
+
return typeof value === 'string' ? value.trim() : undefined;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Organization subfields Google will RETURN but not ACCEPT.
|
|
248
|
+
*
|
|
249
|
+
* Measured, on both createContact and updateContact: an `organizations` entry carrying
|
|
250
|
+
* `startDate` or `endDate` is answered with `500 Internal error encountered`. Every other
|
|
251
|
+
* subfield probed — department, type, jobDescription, symbol, domain, location,
|
|
252
|
+
* phoneticName, costCenter, current — round-trips fine.
|
|
253
|
+
*
|
|
254
|
+
* This is Google's bug, and it puts an edit that carries one of these fields between two
|
|
255
|
+
* bad outcomes: send it and the write fails with a 500 that says nothing about why, or
|
|
256
|
+
* drop it and the contact quietly loses an employment date nobody asked to change.
|
|
257
|
+
*/
|
|
258
|
+
const UNWRITABLE_ORG_FIELDS = ['startDate', 'endDate'];
|
|
259
|
+
/**
|
|
260
|
+
* Stop before the write rather than after, and say what to do about it.
|
|
261
|
+
*
|
|
262
|
+
* The alternative is a bare `500 Internal error encountered` arriving from an operation
|
|
263
|
+
* that only changed a job title, which is not something a caller can act on.
|
|
264
|
+
*/
|
|
265
|
+
function refuseUnwritableOrgFields(orgs) {
|
|
266
|
+
const found = [...new Set(orgs.flatMap(o => UNWRITABLE_ORG_FIELDS.filter(f => o[f] !== undefined)))];
|
|
267
|
+
if (found.length === 0)
|
|
268
|
+
return;
|
|
269
|
+
throw new Error(`This contact's organization record has ${found.join(' and ')}, which Google's People API ` +
|
|
270
|
+
`returns on read and rejects on write with a 500. Changing 'company' or 'jobTitle' would have to ` +
|
|
271
|
+
`send ${found.length > 1 ? 'those fields' : 'that field'} back and fail, or drop ${found.length > 1 ? 'them' : 'it'} ` +
|
|
272
|
+
`and lose employment history nobody asked to change — so this stops instead. ` +
|
|
273
|
+
`Every other field on this contact updates normally. To change the job title, edit it in Google ` +
|
|
274
|
+
`Contacts directly, or clear the employment dates there first.`);
|
|
275
|
+
}
|
|
276
|
+
/** A copy of a Person field entry without the metadata Google owns and writes itself. */
|
|
277
|
+
function withoutMetadata(entry) {
|
|
278
|
+
const { metadata: _ignored, ...rest } = entry;
|
|
279
|
+
return { ...rest };
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Build the Person fields from the flat parameters, and report which Person fields the
|
|
283
|
+
* caller touched.
|
|
284
|
+
*
|
|
285
|
+
* A parameter that is PRESENT but empty is a deliberate clear: the field is reported as
|
|
286
|
+
* touched and gets no entry, which on `update` is how Google is told to empty it. A
|
|
287
|
+
* parameter that is absent is not reported, so `update` never names it and never
|
|
288
|
+
* disturbs it.
|
|
289
|
+
*
|
|
290
|
+
* `existingOrg` matters because `company` and `jobTitle` write to the SAME Person field.
|
|
291
|
+
* Setting only the title would otherwise replace the whole organization entry and drop
|
|
292
|
+
* the employer — a data loss with no error, inside a single field, from a parameter that
|
|
293
|
+
* never mentioned the employer.
|
|
294
|
+
*/
|
|
295
|
+
function buildPerson(params, existingOrgs = []) {
|
|
296
|
+
const person = {};
|
|
297
|
+
const touched = new Set();
|
|
298
|
+
for (const key of WRITABLE) {
|
|
299
|
+
const value = params[key];
|
|
300
|
+
if (value === undefined)
|
|
301
|
+
continue;
|
|
302
|
+
// A non-string here is the worst input this operation can take, because the two
|
|
303
|
+
// halves of an update are derived from different facts: `touched` from whether the
|
|
304
|
+
// parameter ARRIVED, the body from whether it is a usable string. A number reaching
|
|
305
|
+
// `phone` would name phoneNumbers in updatePersonFields and put nothing in the body
|
|
306
|
+
// — which is precisely how Google is told to DELETE the number. The caller asked to
|
|
307
|
+
// set a phone number, the contact loses the one it had, and the response says
|
|
308
|
+
// "Contact updated."
|
|
309
|
+
//
|
|
310
|
+
// The MCP handler does not validate against inputSchema, so nothing upstream stops
|
|
311
|
+
// this. Refusing matches mapSortOrder, which refuses for the same reason.
|
|
312
|
+
if (typeof value !== 'string') {
|
|
313
|
+
throw new Error(`${key} must be a string, got ${Array.isArray(value) ? 'an array' : typeof value}. ` +
|
|
314
|
+
`An empty string clears the field; anything else is stored as written.`);
|
|
315
|
+
}
|
|
316
|
+
touched.add(PERSON_FIELD[key]);
|
|
317
|
+
}
|
|
318
|
+
const name = trimmed(params.name);
|
|
319
|
+
if (name) {
|
|
320
|
+
// Google derives displayName itself; it needs the parts. One word is a given name —
|
|
321
|
+
// guessing a family name from it would invent data.
|
|
322
|
+
const parts = name.split(/\s+/);
|
|
323
|
+
person.names = [parts.length === 1
|
|
324
|
+
? { givenName: parts[0] }
|
|
325
|
+
: { givenName: parts[0], familyName: parts.slice(1).join(' ') }];
|
|
326
|
+
}
|
|
327
|
+
const contactEmail = trimmed(params.contactEmail);
|
|
328
|
+
if (contactEmail)
|
|
329
|
+
person.emailAddresses = [{ value: contactEmail }];
|
|
330
|
+
const phone = trimmed(params.phone);
|
|
331
|
+
if (phone)
|
|
332
|
+
person.phoneNumbers = [{ value: phone }];
|
|
333
|
+
// `company` and `jobTitle` are two parameters over ONE Person field, so writing either
|
|
334
|
+
// rewrites the whole `organizations` array. An earlier version rebuilt that array as
|
|
335
|
+
// `{name, title}` and nothing else, which silently discarded `department`, `type` and
|
|
336
|
+
// `startDate` — fields the read half already had in hand — and dropped every entry
|
|
337
|
+
// after the first. Correcting a job title erased an employment record, and the
|
|
338
|
+
// confirmation looked right because it only ever renders name and title.
|
|
339
|
+
//
|
|
340
|
+
// So the existing entries are carried through and only the two named keys are
|
|
341
|
+
// overwritten. `metadata` is dropped because Google populates it and rejects nothing
|
|
342
|
+
// for its absence.
|
|
343
|
+
if (params.company !== undefined || params.jobTitle !== undefined) {
|
|
344
|
+
refuseUnwritableOrgFields(existingOrgs);
|
|
345
|
+
const [primary = {}, ...others] = existingOrgs;
|
|
346
|
+
const merged = withoutMetadata(primary);
|
|
347
|
+
if (params.company !== undefined) {
|
|
348
|
+
const company = trimmed(params.company);
|
|
349
|
+
if (company)
|
|
350
|
+
merged.name = company;
|
|
351
|
+
else
|
|
352
|
+
delete merged.name;
|
|
353
|
+
}
|
|
354
|
+
if (params.jobTitle !== undefined) {
|
|
355
|
+
const jobTitle = trimmed(params.jobTitle);
|
|
356
|
+
if (jobTitle)
|
|
357
|
+
merged.title = jobTitle;
|
|
358
|
+
else
|
|
359
|
+
delete merged.title;
|
|
360
|
+
}
|
|
361
|
+
const all = [merged, ...others.map(withoutMetadata)].filter(o => Object.keys(o).length > 0);
|
|
362
|
+
if (all.length > 0)
|
|
363
|
+
person.organizations = all;
|
|
364
|
+
}
|
|
365
|
+
const notes = trimmed(params.notes);
|
|
366
|
+
if (notes)
|
|
367
|
+
person.biographies = [{ value: notes, contentType: 'TEXT_PLAIN' }];
|
|
368
|
+
return { person, touched };
|
|
369
|
+
}
|
|
370
|
+
function personBody(params) {
|
|
371
|
+
const rest = { ...params };
|
|
372
|
+
for (const key of WRITABLE)
|
|
373
|
+
delete rest[key];
|
|
374
|
+
const { person } = buildPerson(params);
|
|
375
|
+
// An empty Person is a valid request and a useless contact: Google answers 200 with a
|
|
376
|
+
// record carrying nothing but an id. Refusing is the only way the caller finds out.
|
|
377
|
+
if (Object.keys(person).length === 0) {
|
|
378
|
+
throw new Error('create needs at least one of: name, contactEmail, phone, company, jobTitle, notes. ' +
|
|
379
|
+
'Google would accept an empty contact and return one with no name and no address.');
|
|
380
|
+
}
|
|
381
|
+
return { ...rest, ...person };
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* `update` is a read-modify-write, for two reasons that are not optional.
|
|
385
|
+
*
|
|
386
|
+
* Google requires the contact's current `etag` in the body and rejects the call without
|
|
387
|
+
* it — that is the API refusing to let one caller silently overwrite another's edit.
|
|
388
|
+
*
|
|
389
|
+
* And `updatePersonFields` REPLACES every entry in each field it names. Naming a field
|
|
390
|
+
* with nothing in the body clears it. So the list is derived from what the caller
|
|
391
|
+
* actually passed, never declared: a static list would wipe a phone number every time
|
|
392
|
+
* someone corrected a job title.
|
|
393
|
+
*/
|
|
394
|
+
async function updateContact(params, ctx) {
|
|
395
|
+
const withId = normalizeContactId(params);
|
|
396
|
+
const resourceName = String(withId.resourceName);
|
|
397
|
+
const current = asRec(await call('people', 'people.get', {
|
|
398
|
+
resourceName,
|
|
399
|
+
personFields: 'names,emailAddresses,phoneNumbers,organizations,biographies,metadata',
|
|
400
|
+
}, { account: ctx.account }));
|
|
401
|
+
const { person, touched } = buildPerson(params, entries(current, 'organizations'));
|
|
402
|
+
if (touched.size === 0) {
|
|
403
|
+
throw new Error('update needs at least one of: name, contactEmail, phone, company, jobTitle, notes. ' +
|
|
404
|
+
'Pass an empty string to clear a field.');
|
|
405
|
+
}
|
|
406
|
+
const rest = { ...withId };
|
|
407
|
+
for (const key of WRITABLE)
|
|
408
|
+
delete rest[key];
|
|
409
|
+
return {
|
|
410
|
+
...rest,
|
|
411
|
+
...person,
|
|
412
|
+
etag: current.etag,
|
|
413
|
+
updatePersonFields: [...touched].join(','),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Both search operations require a WARMUP call before they return anything.
|
|
418
|
+
*
|
|
419
|
+
* This is not folklore — Google says it in the Discovery document this server generates
|
|
420
|
+
* from, on `people.searchContacts` and on `otherContacts.search` alike: "Before
|
|
421
|
+
* searching, clients should send a warmup request with an empty query to update the
|
|
422
|
+
* cache." A first search without it comes back empty, which reads exactly like "you have
|
|
423
|
+
* no contact by that name" and is the most convincing wrong answer this tool could give.
|
|
424
|
+
*
|
|
425
|
+
* Once per account per operation is enough for the life of the process; the cache is
|
|
426
|
+
* Google's, and paying for a second round trip on every search would be worse than the
|
|
427
|
+
* problem.
|
|
428
|
+
*/
|
|
429
|
+
const warming = new Map();
|
|
430
|
+
function warmupFirst(resource) {
|
|
431
|
+
return async (params, ctx) => {
|
|
432
|
+
const key = `${ctx.account}:${resource}`;
|
|
433
|
+
// The in-flight promise is cached, not a "we tried" flag. Recording the attempt up
|
|
434
|
+
// front made a single transient 429 permanent: warmup was skipped for the rest of
|
|
435
|
+
// the process, every later search ran cold, and a cold search returns EMPTY — which
|
|
436
|
+
// renders as "no match", the convincing wrong answer this whole mechanism exists to
|
|
437
|
+
// prevent. It also let a second concurrent search past a warmup still in flight.
|
|
438
|
+
let inFlight = warming.get(key);
|
|
439
|
+
if (!inFlight) {
|
|
440
|
+
inFlight = call('people', resource, { query: '', readMask: params.readMask }, { account: ctx.account })
|
|
441
|
+
.then(() => { }, () => {
|
|
442
|
+
// A cache hint, not a precondition: forget the failure so the next search
|
|
443
|
+
// retries. If it failed for a reason that matters — no scope, no network —
|
|
444
|
+
// the real search is about to fail the same way and say so properly.
|
|
445
|
+
warming.delete(key);
|
|
446
|
+
});
|
|
447
|
+
warming.set(key, inFlight);
|
|
448
|
+
}
|
|
449
|
+
await inFlight;
|
|
450
|
+
return params;
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/** Test seam: the warmup is per-process state, and a test must be able to reset it. */
|
|
454
|
+
export function resetWarmupCache() {
|
|
455
|
+
warming.clear();
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Confirm a write by saying what was written and where it lives.
|
|
459
|
+
*
|
|
460
|
+
* The new id is the part that matters: it is the only handle on the contact just made,
|
|
461
|
+
* and `get`, and any future update, need it. Google returns it and the generic action
|
|
462
|
+
* formatter would drop it.
|
|
463
|
+
*/
|
|
464
|
+
function formatContactAction(data, ctx) {
|
|
465
|
+
// deleteContact returns an empty body: there is no person left to render, and the id
|
|
466
|
+
// the caller passed is the only thing left to name.
|
|
467
|
+
if (ctx.operation === 'delete') {
|
|
468
|
+
// ctx.params holds what the CALLER passed, before normalization — so a caller who
|
|
469
|
+
// wrote `c36` would read "Contact deleted: c36" while `people/c36` was deleted. The
|
|
470
|
+
// receipt has to name the thing that was actually removed.
|
|
471
|
+
const id = String(normalizeContactId({ resourceName: ctx.params.contactId }).resourceName ?? '');
|
|
472
|
+
return { text: `Contact deleted: ${id}`, refs: { contactId: id, deleted: true } };
|
|
473
|
+
}
|
|
474
|
+
const person = asRec(data);
|
|
475
|
+
const id = personId(person);
|
|
476
|
+
const detail = formatPersonDetail(person);
|
|
477
|
+
return {
|
|
478
|
+
text: `Contact ${ctx.operation === 'create' ? 'created' : 'updated'}.\n\n${detail.text}`,
|
|
479
|
+
refs: { id, contactId: id },
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
export const contactsPatch = {
|
|
483
|
+
beforeExecute: {
|
|
484
|
+
get: normalizeContactId,
|
|
485
|
+
list: mapSortOrder,
|
|
486
|
+
create: personBody,
|
|
487
|
+
update: updateContact,
|
|
488
|
+
delete: normalizeContactId,
|
|
489
|
+
search: warmupFirst('people.searchContacts'),
|
|
490
|
+
searchOther: warmupFirst('otherContacts.search'),
|
|
491
|
+
},
|
|
492
|
+
formatList: formatPersonList,
|
|
493
|
+
formatDetail: formatPersonDetail,
|
|
494
|
+
formatAction: formatContactAction,
|
|
495
|
+
};
|
|
496
|
+
//# sourceMappingURL=patch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patch.js","sourceRoot":"","sources":["../../../src/services/contacts/patch.ts"],"names":[],"mappings":"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"}
|