@sprigr/apps-app-sdk 0.3.0 → 0.5.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 +120 -1
- package/dist/index.js +34 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -234,6 +234,72 @@ declare function resolvePlatformWebhookBase(env: PlatformHostEnv, override?: str
|
|
|
234
234
|
* form should pre-translate before calling.
|
|
235
235
|
*/
|
|
236
236
|
declare function buildMarketplaceWebhookUrl(env: PlatformHostEnv, installId: string, topicPath: string, override?: string | null): string;
|
|
237
|
+
/**
|
|
238
|
+
* Is this URL (or bare hostname) served by a STAGING host?
|
|
239
|
+
*
|
|
240
|
+
* Every Sprigr staging host is `staging-<something>.sprigr.com`:
|
|
241
|
+
* `staging-apps` (per-install app sites), `staging-sites` (the
|
|
242
|
+
* preview-token host from `get_website_preview`), `staging-team` (the
|
|
243
|
+
* portal), `staging-webhooks` (the platform base). No prod host carries
|
|
244
|
+
* a `staging-` label.
|
|
245
|
+
*
|
|
246
|
+
* Tests the HOSTNAME, not a substring of the whole URL. Eight apps
|
|
247
|
+
* previously hand-rolled
|
|
248
|
+
*
|
|
249
|
+
* reqUrl.includes('staging-apps.sprigr.com') ||
|
|
250
|
+
* reqUrl.includes('staging-team.sprigr.com')
|
|
251
|
+
*
|
|
252
|
+
* which was wrong in BOTH directions. It missed `staging-sites`, so a
|
|
253
|
+
* staging install served from the preview host built an authorize URL
|
|
254
|
+
* pointing at the PRODUCTION bouncer — which cannot resolve a staging
|
|
255
|
+
* install id, so the connect died at the bouncer with a confusing error
|
|
256
|
+
* (observed live 2026-07-25 by reading the Location header off a
|
|
257
|
+
* deployed /oauth/start). And because it matched the whole URL, a PROD
|
|
258
|
+
* install hit with `?return_to=https://staging-team.sprigr.com/x` was
|
|
259
|
+
* classified as staging. Parsing the hostname closes both.
|
|
260
|
+
*
|
|
261
|
+
* Unparseable input resolves to `false` (prod), so a caller can never
|
|
262
|
+
* accidentally point production traffic at staging.
|
|
263
|
+
*/
|
|
264
|
+
declare function isStagingHost(urlOrHostname: string | null | undefined): boolean;
|
|
265
|
+
/**
|
|
266
|
+
* Signals an app can offer to decide which bouncer to use. Pass whatever
|
|
267
|
+
* the call site actually has:
|
|
268
|
+
*
|
|
269
|
+
* - `reqUrl` an inbound browser request URL (the /oauth/start route).
|
|
270
|
+
* - `platformBase` the platform-stamped `SPRIGR_PLATFORM_BASE`, for tool
|
|
271
|
+
* handlers dispatched on /__sprigr/tool/* where there is
|
|
272
|
+
* no browser origin. Staging value is
|
|
273
|
+
* `https://staging-webhooks.sprigr.com`.
|
|
274
|
+
* - `override` an explicit per-install redirect URI / bouncer base.
|
|
275
|
+
* Always wins; used for local dev and canaries.
|
|
276
|
+
*
|
|
277
|
+
* Staging wins if ANY supplied signal says staging.
|
|
278
|
+
*/
|
|
279
|
+
interface OAuthBouncerSignals {
|
|
280
|
+
reqUrl?: string | null;
|
|
281
|
+
platformBase?: string | null;
|
|
282
|
+
override?: string | null;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* The bouncer ORIGIN for the environment serving this request, e.g.
|
|
286
|
+
* `https://staging-oauth-bouncer.sprigr.com`. Never has a trailing slash.
|
|
287
|
+
*
|
|
288
|
+
* `override` short-circuits everything (trailing slash stripped).
|
|
289
|
+
*/
|
|
290
|
+
declare function resolveOAuthBouncerBase(signals?: OAuthBouncerSignals): string;
|
|
291
|
+
/**
|
|
292
|
+
* The full bouncer CALLBACK URL for a provider, which is the value that
|
|
293
|
+
* goes in an authorize URL's `redirect_uri` and must be registered as a
|
|
294
|
+
* redirect URI on the provider's developer app:
|
|
295
|
+
*
|
|
296
|
+
* resolveOAuthBouncerCallbackUrl('simpro', { reqUrl: req.url })
|
|
297
|
+
* // -> "https://staging-oauth-bouncer.sprigr.com/simpro/oauth/callback"
|
|
298
|
+
*
|
|
299
|
+
* `override` is returned verbatim (no provider path appended) — an app
|
|
300
|
+
* pointing at a local bouncer supplies the complete callback URL.
|
|
301
|
+
*/
|
|
302
|
+
declare function resolveOAuthBouncerCallbackUrl(provider: string, signals?: OAuthBouncerSignals): string;
|
|
237
303
|
|
|
238
304
|
/**
|
|
239
305
|
* Talking to the platform from BOTH execution contexts.
|
|
@@ -462,6 +528,59 @@ declare function parseActor(args: unknown): Actor | undefined;
|
|
|
462
528
|
* treat that as "no caller identity" and fail closed.
|
|
463
529
|
*/
|
|
464
530
|
declare function actorKey(actor: Actor | undefined): string | null;
|
|
531
|
+
/**
|
|
532
|
+
* Mailbox-owner attribution for one `env.SPRIGR.inbox.append` /
|
|
533
|
+
* `env.SPRIGR.inbox.folders` batch.
|
|
534
|
+
*
|
|
535
|
+
* Exactly ONE of the two fields must be set:
|
|
536
|
+
*
|
|
537
|
+
* - `platformUserId` — OIDC sub of the user whose mailbox the batch was
|
|
538
|
+
* synced from (a personal mailbox connection).
|
|
539
|
+
* - `agentId` — agent id for an agent-keyed connection (a shared /
|
|
540
|
+
* departmental mailbox connected through a shared agent rather than a
|
|
541
|
+
* person).
|
|
542
|
+
*
|
|
543
|
+
* Why this exists: a mail app install is shared by the whole company, and
|
|
544
|
+
* several users can each connect their own mailbox to it. The platform files
|
|
545
|
+
* every appended message under the ONE integration behind the install, so
|
|
546
|
+
* without attribution it cannot tell whose mailbox a batch came from and has
|
|
547
|
+
* historically granted thread visibility to every connected user. Passing
|
|
548
|
+
* `owner` pins each synced thread's visibility to exactly the mailbox owner:
|
|
549
|
+
* user connections stay private to that user, agent-keyed connections stay
|
|
550
|
+
* visible to that agent's audience.
|
|
551
|
+
*
|
|
552
|
+
* This is the OWNER OF THE MAILBOX (the connection identity the app keyed
|
|
553
|
+
* the sync on), NOT the dispatch caller — a scheduled poll has no caller at
|
|
554
|
+
* all yet must still attribute every batch. Populate it from the per-actor
|
|
555
|
+
* connection row the sync loop is iterating, e.g.:
|
|
556
|
+
*
|
|
557
|
+
* const owner = row.sprigr_user_id
|
|
558
|
+
* ? { platformUserId: row.sprigr_user_id }
|
|
559
|
+
* : { agentId: row.sprigr_agent_id };
|
|
560
|
+
* await env.SPRIGR.inbox.append({ channel, messages, owner });
|
|
561
|
+
*
|
|
562
|
+
* Mail apps must send it on EVERY append/folders call (sync poll, push
|
|
563
|
+
* delivery, backfill, folder reports). Batches without it fall back to the
|
|
564
|
+
* platform's legacy integration-level resolution, which on a multi-user
|
|
565
|
+
* install pins nobody (fail closed) rather than everyone.
|
|
566
|
+
*
|
|
567
|
+
* Hosts older than the attribution surface ignore the field entirely, so
|
|
568
|
+
* sending it is always safe.
|
|
569
|
+
*/
|
|
570
|
+
interface InboxOwner {
|
|
571
|
+
/** OIDC sub (platform user id) of the mailbox's owner. */
|
|
572
|
+
platformUserId?: string;
|
|
573
|
+
/** Agent id for an agent-keyed (shared-mailbox) connection. */
|
|
574
|
+
agentId?: string;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Inverse of `actorKey`: turn a stored `u:<platformUserId>` / `a:<agentId>`
|
|
578
|
+
* key back into an `InboxOwner`. For apps that persist the connection
|
|
579
|
+
* identity as an actor-key string and need to attribute inbox batches from
|
|
580
|
+
* it. Returns null for null/empty/unrecognised input — callers should treat
|
|
581
|
+
* that as "owner unknown" and omit the field rather than guess.
|
|
582
|
+
*/
|
|
583
|
+
declare function ownerFromActorKey(key: string | null | undefined): InboxOwner | null;
|
|
465
584
|
|
|
466
585
|
/**
|
|
467
586
|
* App-scoped R2 file storage (platform feature, 2026-06).
|
|
@@ -873,4 +992,4 @@ interface ExecutionContextLike {
|
|
|
873
992
|
}
|
|
874
993
|
type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
|
|
875
994
|
|
|
876
|
-
export { type Actor, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InstallBridge, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, type OAuthState, type PlatformHostEnv, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type ScheduleArgs, type SprigrDataApi, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, appFileUrl, base64ToBytes, buildMarketplaceWebhookUrl, bytesToBase64, canEmit, constantTimeEqual, createMarketplaceEmitter, decodeState, deleteAppFile, describeMissingBridge, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, getAppFile, hmacSha256Hex, installTokenPost, listAppFiles, overlaySprigr, parseActor, putAppFile, putAppFileStream, randomHex, resolveInstallBridge, resolvePlatformWebhookBase, withSprigrEmitFallback };
|
|
995
|
+
export { type Actor, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InboxOwner, type InstallBridge, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, type OAuthBouncerSignals, type OAuthState, type PlatformHostEnv, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type ScheduleArgs, type SprigrDataApi, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, appFileUrl, base64ToBytes, buildMarketplaceWebhookUrl, bytesToBase64, canEmit, constantTimeEqual, createMarketplaceEmitter, decodeState, deleteAppFile, describeMissingBridge, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, getAppFile, hmacSha256Hex, installTokenPost, isStagingHost, listAppFiles, overlaySprigr, ownerFromActorKey, parseActor, putAppFile, putAppFileStream, randomHex, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, withSprigrEmitFallback };
|
package/dist/index.js
CHANGED
|
@@ -165,6 +165,30 @@ function buildMarketplaceWebhookUrl(env, installId, topicPath, override) {
|
|
|
165
165
|
const base = resolvePlatformWebhookBase(env, override);
|
|
166
166
|
return `${base}/webhook/marketplace/${installId}/${topicPath}`;
|
|
167
167
|
}
|
|
168
|
+
var OAUTH_BOUNCER_PROD = "https://oauth-bouncer.sprigr.com";
|
|
169
|
+
var OAUTH_BOUNCER_STAGING = "https://staging-oauth-bouncer.sprigr.com";
|
|
170
|
+
function isStagingHost(urlOrHostname) {
|
|
171
|
+
const raw = (urlOrHostname ?? "").trim();
|
|
172
|
+
if (!raw) return false;
|
|
173
|
+
let hostname;
|
|
174
|
+
try {
|
|
175
|
+
hostname = new URL(raw).hostname;
|
|
176
|
+
} catch {
|
|
177
|
+
hostname = /^[a-z0-9.-]+$/i.test(raw) ? raw : "";
|
|
178
|
+
}
|
|
179
|
+
return /(^|\.)staging-/.test(hostname);
|
|
180
|
+
}
|
|
181
|
+
function resolveOAuthBouncerBase(signals = {}) {
|
|
182
|
+
const override = (signals.override ?? "").trim();
|
|
183
|
+
if (override) return override.replace(/\/$/, "");
|
|
184
|
+
const isStaging = isStagingHost(signals.reqUrl) || isStagingHost(signals.platformBase);
|
|
185
|
+
return isStaging ? OAUTH_BOUNCER_STAGING : OAUTH_BOUNCER_PROD;
|
|
186
|
+
}
|
|
187
|
+
function resolveOAuthBouncerCallbackUrl(provider, signals = {}) {
|
|
188
|
+
const override = (signals.override ?? "").trim();
|
|
189
|
+
if (override) return override;
|
|
190
|
+
return `${resolveOAuthBouncerBase(signals)}/${provider}/oauth/callback`;
|
|
191
|
+
}
|
|
168
192
|
|
|
169
193
|
// src/wfp-bridge.ts
|
|
170
194
|
function resolveInstallBridge(env) {
|
|
@@ -308,6 +332,12 @@ function actorKey(actor) {
|
|
|
308
332
|
if (actor.agentId) return `a:${actor.agentId}`;
|
|
309
333
|
return null;
|
|
310
334
|
}
|
|
335
|
+
function ownerFromActorKey(key) {
|
|
336
|
+
if (!key) return null;
|
|
337
|
+
if (key.startsWith("u:") && key.length > 2) return { platformUserId: key.slice(2) };
|
|
338
|
+
if (key.startsWith("a:") && key.length > 2) return { agentId: key.slice(2) };
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
311
341
|
|
|
312
342
|
// src/app-files.ts
|
|
313
343
|
var DEFAULT_PLATFORM_BASE = "https://webhooks.sprigr.com";
|
|
@@ -417,13 +447,17 @@ export {
|
|
|
417
447
|
getAppFile,
|
|
418
448
|
hmacSha256Hex,
|
|
419
449
|
installTokenPost,
|
|
450
|
+
isStagingHost,
|
|
420
451
|
listAppFiles,
|
|
421
452
|
overlaySprigr,
|
|
453
|
+
ownerFromActorKey,
|
|
422
454
|
parseActor,
|
|
423
455
|
putAppFile,
|
|
424
456
|
putAppFileStream,
|
|
425
457
|
randomHex,
|
|
426
458
|
resolveInstallBridge,
|
|
459
|
+
resolveOAuthBouncerBase,
|
|
460
|
+
resolveOAuthBouncerCallbackUrl,
|
|
427
461
|
resolvePlatformWebhookBase,
|
|
428
462
|
withSprigrEmitFallback
|
|
429
463
|
};
|