The Coline App SDK: apps, tools, file types and surfaces that run inside Coline workspaces, and public websites with Coline Sites. TypeScript, with an in-memory test workspace. MIT.
| 5 | * Account connections: OAuth 2.0 accounts on external services, held by |
| 6 | * Coline and used by the app without the app ever seeing a token. |
| 7 | * |
| 8 | * `scope: "user"` gives every member their own connection (each person's |
| 9 | * GitHub); `scope: "workspace"` is one shared connection an admin makes. |
| 10 | * Coline runs the authorization-code flow (PKCE by default) in a host |
| 11 | * window, stores tokens encrypted, refreshes them, and injects the access |
| 12 | * token into `net.fetch` headers through `{{connection:KEY}}` placeholders |
| 13 | * — only for requests to the connection's declared hosts. |
| 14 | * |
| 15 | * The OAuth client secret is a developer secret (set once in the developer |
| 16 | * console), never an install secret, so store apps work in every |
| 17 | * workspace without each admin registering an OAuth client. |
| 18 | */ |
| 19 |
| 20 | export const connectionKeySchema = z |
| 21 | .string() |
| 22 | .trim() |
| 23 | .regex(/^[a-z][a-z0-9_-]*$/, "Connection keys are lowercase identifiers.") |
| 24 | .max(60); |
| 25 |
| 26 | const httpsUrlSchema = z |
| 27 | .url() |
| 28 | .max(2000) |
| 29 | .refine((value) => value.startsWith("https://"), "OAuth URLs must use https."); |
| 30 |
| 31 | const hostSchema = z |
| 32 | .string() |
| 33 | .trim() |
| 34 | .toLowerCase() |
| 35 | .regex( |
| 36 | /^(\*\.)?([a-z0-9-]+\.)+[a-z]{2,}$/, |
| 37 | "Connection hosts are domains, optionally with a leading `*.` wildcard.", |
| 38 | ) |
| 39 | .max(253); |
| 40 |
| 41 | export const appConnectionSchema = z.strictObject({ |
| 42 | key: connectionKeySchema, |
| 43 | name: z.string().trim().min(1).max(120), |
| 44 | description: z.string().trim().max(500).optional(), |
| 45 | scope: z.enum(["user", "workspace"]).default("user"), |
| 46 | /** Hosts this connection's token may be sent to. Must be in `network.allowedHosts`. */ |
| 47 | hosts: z.array(hostSchema).min(1).max(10), |
| 48 | oauth2: z.strictObject({ |
| 49 | authorizeUrl: httpsUrlSchema, |
| 50 | tokenUrl: httpsUrlSchema, |
| 51 | clientId: z.string().trim().min(1).max(500), |
| 52 | /** Developer secret holding the OAuth client secret. Omit for public PKCE clients. */ |
| 53 | clientSecret: secretKeySchema.optional(), |
| 54 | scopes: z.array(z.string().trim().min(1).max(200)).max(50).default([]), |
| 55 | scopeSeparator: z.string().max(4).default(" "), |
| 56 | pkce: z.boolean().default(true), |
| 57 | /** Extra query parameters for the authorize URL (e.g. `access_type: offline`). */ |
| 58 | authorizeParams: z.record(z.string().max(80), z.string().max(500)).default({}), |
| 59 | /** How the client authenticates at the token endpoint. */ |
| 60 | tokenAuth: z.enum(["body", "basic"]).default("body"), |
| 61 | /** Optional endpoint returning the connected account, for display. */ |
| 62 | accountUrl: httpsUrlSchema.optional(), |
| 63 | /** Dotted path into the account response used as the account label. */ |
| 64 | accountLabelPath: z.string().trim().max(200).optional(), |
| 65 | }), |
| 66 | }); |
| 67 | export type AppConnection = z.infer<typeof appConnectionSchema>; |
| 68 | export type AppConnectionInput = z.input<typeof appConnectionSchema>; |
| 69 |
| 70 | export const appConnectionsSchema = z |
| 71 | .array(appConnectionSchema) |
| 72 | .max(10) |
| 73 | .default([]) |
| 74 | .refine( |
| 75 | (connections) => |
| 76 | new Set(connections.map((connection) => connection.key)).size === connections.length, |
| 77 | "Connection keys must be unique.", |
| 78 | ); |
| 79 |