@segmentstream-cloud/app 0.15.0 → 0.16.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.
@@ -1,7 +1,7 @@
1
1
  import { AGENT_CONNECT_PAGE, CONSENT_PAGE } from "./pages.js";
2
2
  import { matchesPathPrefix } from "./proxy.js";
3
3
  /** Pages where a signed-in member lets an agent act for them. */
4
- const DECISION_PAGES = [CONSENT_PAGE, AGENT_CONNECT_PAGE];
4
+ const DECISION_PAGES = [CONSENT_PAGE, AGENT_CONNECT_PAGE, "/sign-in", "/api/oauth/consent"];
5
5
  /**
6
6
  * The proxy, with the decision pages unframeable: no other site may show them in a frame and trick
7
7
  * a member into clicking Allow or Approve. Every app gets this from the SDK, whatever its Next.js
@@ -12,8 +12,14 @@ export function withUnframeablePages(proxy) {
12
12
  const response = await proxy(request);
13
13
  const pathname = request.nextUrl.pathname;
14
14
  if (DECISION_PAGES.some((page) => matchesPathPrefix(pathname, page))) {
15
+ response.headers.set("cache-control", "no-store");
16
+ response.headers.set("referrer-policy", "no-referrer");
15
17
  response.headers.set("x-frame-options", "DENY");
16
- response.headers.set("content-security-policy", "frame-ancestors 'none'");
18
+ const policy = response.headers.get("content-security-policy");
19
+ response.headers.set("content-security-policy", policy === null
20
+ ? "frame-ancestors 'none'"
21
+ : policy.replace(/frame-ancestors[^;]*/g, "frame-ancestors 'none'") +
22
+ (policy.includes("frame-ancestors") ? "" : "; frame-ancestors 'none'"));
17
23
  }
18
24
  return response;
19
25
  };
package/dist/logo.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const SEGMENTSTREAM_LOGO_URI: string;
package/dist/logo.js ADDED
@@ -0,0 +1,25 @@
1
+ // Official asset: apps/website/public/segmentstream-logo-black.svg. Keep it in sync.
2
+ const SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="283 330 755 162" fill="none">
3
+ <!-- S icon (black) -->
4
+ <path fill="#18181b" d="M342.57,485.52l46.86-61.07c4.2-5.48,3.17-13.33-2.31-17.54l-1.08-0.83c-6.34-4.86-15.42-3.67-20.29,2.67l-29.25,38.12c-9.07,11.82-6.84,28.75,4.98,37.82L342.57,485.52z"/>
5
+ <path fill="#18181b" d="M393.15,410.11c-0.16-15.51-12.79-28.04-28.34-28.04H349.9c-4.94,0-8.95,4.01-8.95,8.95v19.39h42.52l0.01,0.01c0.09,0,0.19-0.01,0.28-0.01c4.37,0,7.9,3.54,7.9,7.9c0,0.9-0.15,1.77-0.43,2.58c0,0,1.25-3.04,1.63-5.72c0.24-1.69,0.29-3.97,0.3-4.77v0c0-0.03,0-0.06,0-0.09C393.15,410.18,393.15,410.11,393.15,410.11z"/>
6
+ <path fill="#18181b" d="M339.43,335.27l-46.86,61.07c-4.2,5.48-3.17,13.33,2.31,17.54l1.08,0.83c6.34,4.86,15.42,3.67,20.29-2.67l29.25-38.12c9.07-11.82,6.84-28.75-4.98-37.82L339.43,335.27z"/>
7
+ <path fill="#18181b" d="M288.85,410.68c0.16,15.51,12.79,28.04,28.34,28.04h14.91c4.94,0,8.95-4.01,8.95-8.95v-19.39h-42.52l-0.01-0.01c-0.09,0-0.19,0.01-0.28,0.01c-4.37,0-7.9-3.54-7.9-7.9c0-0.9,0.15-1.77,0.43-2.58c0,0-1.25,3.04-1.63,5.72c-0.24,1.69-0.29,3.97-0.3,4.77v0c0,0.03,0,0.06,0,0.09C288.85,410.6,288.85,410.68,288.85,410.68z"/>
8
+ <!-- Wordmark: segmentstream (black) -->
9
+ <g fill="#18181b">
10
+ <path d="M452.61,400.96c-1.03-1.65-2.99-3.72-6.2-3.72c-1.45,0-2.48,0.52-3.1,1.14c-0.52,0.52-0.93,1.34-0.93,2.27c0,1.14,0.52,1.86,1.45,2.48c0.62,0.41,1.24,0.62,3.41,1.76l5.16,2.58c2.27,1.14,4.44,2.27,6.09,4.03c2.38,2.38,3.2,5.06,3.2,8.06c0,3.92-1.45,7.33-3.82,9.91c-3.41,3.72-7.64,4.85-12.19,4.85c-2.79,0-6.3-0.41-9.4-2.79c-2.48-1.86-4.54-4.85-5.58-7.95l8.26-3.92c0.52,1.45,1.45,3.3,2.38,4.13c0.62,0.62,2.07,1.65,4.44,1.65c1.55,0,3.2-0.52,4.13-1.45c0.83-0.83,1.45-2.17,1.45-3.41c0-1.24-0.41-2.27-1.34-3.3c-1.24-1.24-2.68-1.96-4.34-2.68l-3.72-1.76c-1.96-0.93-4.34-2.07-5.99-3.72c-2.27-2.17-3.1-4.54-3.1-7.33c0-3.51,1.34-6.92,3.61-9.29c1.96-2.07,5.27-4.13,10.64-4.13c3.1,0,5.58,0.62,8.05,2.27c1.45,0.93,3.41,2.58,4.96,5.37L452.61,400.96z"/>
11
+ <path d="M509.71,423.06c-1.86,3.3-4.54,6.2-7.33,7.95c-3.51,2.27-8.16,3.3-12.81,3.3c-5.78,0-10.53-1.45-14.66-5.58c-4.13-4.13-6.4-10.12-6.4-16.63c0-6.71,2.37-13.22,6.92-17.86c3.61-3.61,8.36-5.89,14.56-5.89c6.92,0,11.36,2.99,13.94,5.68c5.58,5.78,6.09,13.84,6.09,18.38v1.24h-31.19c0.21,3.1,1.55,6.61,3.72,8.78c2.38,2.38,5.27,2.79,7.74,2.79c2.79,0,4.85-0.62,6.92-2.17c1.76-1.34,3.1-3.1,4.03-4.75L509.71,423.06z M499.7,405.4c-0.41-2.38-1.55-4.44-3.1-5.89c-1.34-1.24-3.51-2.48-6.82-2.48c-3.51,0-5.78,1.45-7.12,2.79c-1.45,1.34-2.58,3.41-3.1,5.58H499.7z"/>
12
+ <path d="M552.37,389.5h10.12v39.96c0,10.22-1.14,17.55-7.13,22.62c-2.79,2.27-7.33,4.65-14.46,4.65c-5.06,0-10.12-1.24-14.25-5.37c-3.2-3.2-5.68-8.26-6.4-13.53h9.81c0.41,2.38,1.65,5.16,3.2,6.81c1.24,1.34,3.72,3,7.85,3c4.23,0,6.71-1.65,7.95-2.89c3.3-3.2,3.3-7.75,3.3-10.95v-5.68c-4.65,5.89-10.33,6.2-13.22,6.2c-5.58,0-10.22-1.55-14.35-5.89c-3.92-4.13-6.51-10.02-6.51-17.25c0-7.95,3-13.42,6.4-16.83c4.34-4.44,9.5-5.99,14.77-5.99c4.34,0,9.19,1.45,12.91,5.89V389.5z M533.05,400.34c-2.58,2.38-4.44,6.4-4.44,11.05c0,4.34,1.65,8.57,4.34,10.95c1.86,1.65,4.65,2.89,7.75,2.89c3.2,0,5.99-0.93,8.47-3.51c2.99-3.2,3.82-6.92,3.82-10.22c0-4.85-1.55-8.47-4.13-10.95c-2.48-2.48-5.27-3.1-8.16-3.1C537.91,397.45,535.33,398.17,533.05,400.34z"/>
13
+ <path d="M575.29,389.5h10.12v4.03c3.41-4.13,7.43-5.16,10.74-5.16c4.54,0,8.78,2.17,11.26,6.4c3.61-5.16,9.09-6.4,12.91-6.4c5.27,0,9.91,2.48,12.39,6.82c0.83,1.45,2.27,4.65,2.27,10.95v26.85h-10.12v-23.96c0-4.85-0.52-6.82-0.93-7.75c-0.62-1.65-2.17-3.82-5.78-3.82c-2.48,0-4.65,1.34-5.99,3.2c-1.76,2.48-1.96,6.2-1.96,9.91v22.41h-10.12v-23.96c0-4.85-0.52-6.82-0.93-7.75c-0.62-1.65-2.17-3.82-5.78-3.82c-2.48,0-4.65,1.34-5.99,3.2c-1.76,2.48-1.96,6.2-1.96,9.91v22.41h-10.12V389.5z"/>
14
+ <path d="M686.72,423.06c-1.86,3.3-4.54,6.2-7.33,7.95c-3.51,2.27-8.16,3.3-12.8,3.3c-5.78,0-10.53-1.45-14.66-5.58c-4.13-4.13-6.4-10.12-6.4-16.63c0-6.71,2.38-13.22,6.92-17.86c3.61-3.61,8.36-5.89,14.56-5.89c6.92,0,11.36,2.99,13.94,5.68c5.58,5.78,6.09,13.84,6.09,18.38v1.24h-31.19c0.21,3.1,1.55,6.61,3.72,8.78c2.38,2.38,5.27,2.79,7.75,2.79c2.79,0,4.85-0.62,6.92-2.17c1.76-1.34,3.1-3.1,4.03-4.75L686.72,423.06z M676.7,405.4c-0.41-2.38-1.55-4.44-3.1-5.89c-1.34-1.24-3.51-2.48-6.82-2.48c-3.51,0-5.78,1.45-7.13,2.79c-1.45,1.34-2.58,3.41-3.1,5.58H676.7z"/>
15
+ <path d="M697.46,389.5h10.12v4.03c4.34-4.85,9.29-5.16,11.77-5.16c8.05,0,11.46,4.03,12.81,5.89c1.65,2.38,2.68,5.37,2.68,12.08v26.64h-10.12v-24.16c0-11.05-4.13-11.36-7.95-11.36c-4.54,0-9.19,0.62-9.19,14.66v20.86h-10.12V389.5z"/>
16
+ <path d="M758.08,398.79v34.18h-10.12v-34.18h-4.23v-9.29h4.23v-15.9h10.12v15.9h7.75v9.29H758.08z"/>
17
+ <path d="M791.64,400.96c-1.03-1.65-2.99-3.72-6.2-3.72c-1.45,0-2.48,0.52-3.1,1.14c-0.52,0.52-0.93,1.34-0.93,2.27c0,1.14,0.52,1.86,1.45,2.48c0.62,0.41,1.24,0.62,3.41,1.76l5.16,2.58c2.27,1.14,4.44,2.27,6.09,4.03c2.38,2.38,3.2,5.06,3.2,8.06c0,3.92-1.45,7.33-3.82,9.91c-3.41,3.72-7.64,4.85-12.19,4.85c-2.79,0-6.3-0.41-9.4-2.79c-2.48-1.86-4.54-4.85-5.58-7.95l8.26-3.92c0.52,1.45,1.45,3.3,2.38,4.13c0.62,0.62,2.07,1.65,4.44,1.65c1.55,0,3.2-0.52,4.13-1.45c0.83-0.83,1.45-2.17,1.45-3.41c0-1.24-0.41-2.27-1.34-3.3c-1.24-1.24-2.69-1.96-4.34-2.68l-3.72-1.76c-1.96-0.93-4.34-2.07-5.99-3.72c-2.27-2.17-3.1-4.54-3.1-7.33c0-3.51,1.34-6.92,3.61-9.29c1.96-2.07,5.27-4.13,10.64-4.13c3.1,0,5.58,0.62,8.05,2.27c1.45,0.93,3.41,2.58,4.96,5.37L791.64,400.96z"/>
18
+ <path d="M820.25,398.79v34.18h-10.12v-34.18h-4.23v-9.29h4.23v-15.9h10.12v15.9h7.75v9.29H820.25z"/>
19
+ <path d="M834.81,389.5h10.12v3.92c1.14-1.34,2.79-2.79,4.23-3.61c1.96-1.14,3.92-1.45,6.2-1.45c2.48,0,5.16,0.41,7.95,2.07l-4.13,9.19c-2.27-1.45-4.13-1.55-5.16-1.55c-2.17,0-4.34,0.31-6.3,2.38c-2.79,2.99-2.79,7.13-2.79,10.02v22.51h-10.12V389.5z"/>
20
+ <path d="M904.83,423.06c-1.86,3.3-4.54,6.2-7.33,7.95c-3.51,2.27-8.16,3.3-12.8,3.3c-5.78,0-10.53-1.45-14.66-5.58c-4.13-4.13-6.4-10.12-6.4-16.63c0-6.71,2.38-13.22,6.92-17.86c3.61-3.61,8.36-5.89,14.56-5.89c6.92,0,11.36,2.99,13.94,5.68c5.58,5.78,6.09,13.84,6.09,18.38v1.24h-31.19c0.21,3.1,1.55,6.61,3.72,8.78c2.38,2.38,5.27,2.79,7.75,2.79c2.79,0,4.85-0.62,6.92-2.17c1.76-1.34,3.1-3.1,4.03-4.75L904.83,423.06z M894.81,405.4c-0.41-2.38-1.55-4.44-3.1-5.89c-1.34-1.24-3.51-2.48-6.82-2.48c-3.51,0-5.78,1.45-7.13,2.79c-1.45,1.34-2.58,3.41-3.1,5.58H894.81z"/>
21
+ <path d="M946.38,389.5h10.12v43.48h-10.12v-4.54c-4.44,5.16-9.91,5.89-13.01,5.89c-13.43,0-21.07-11.15-21.07-23.13c0-14.15,9.71-22.82,21.17-22.82c3.2,0,8.78,0.83,12.91,6.2V389.5z M922.63,411.39c0,7.54,4.75,13.84,12.08,13.84c6.4,0,12.29-4.65,12.29-13.74c0-9.5-5.89-14.04-12.29-14.04C927.38,397.45,922.63,403.64,922.63,411.39z"/>
22
+ <path d="M969.31,389.5h10.12v4.03c3.41-4.13,7.43-5.16,10.74-5.16c4.54,0,8.78,2.17,11.26,6.4c3.62-5.16,9.09-6.4,12.91-6.4c5.27,0,9.91,2.48,12.39,6.82c0.83,1.45,2.27,4.65,2.27,10.95v26.85h-10.12v-23.96c0-4.85-0.52-6.82-0.93-7.75c-0.62-1.65-2.17-3.82-5.78-3.82c-2.48,0-4.65,1.34-5.99,3.2c-1.76,2.48-1.96,6.2-1.96,9.91v22.41H994.1v-23.96c0-4.85-0.52-6.82-0.93-7.75c-0.62-1.65-2.17-3.82-5.78-3.82c-2.48,0-4.65,1.34-5.99,3.2c-1.76,2.48-1.96,6.2-1.96,9.91v22.41h-10.12V389.5z"/>
23
+ </g>
24
+ </svg>`;
25
+ export const SEGMENTSTREAM_LOGO_URI = `data:image/svg+xml,${encodeURIComponent(SVG)}`;
@@ -0,0 +1,6 @@
1
+ /** Keep a copyable PKCE-bound code visible while trying the client's loopback callback. */
2
+ export declare function oauthCompletion(options: {
3
+ readonly redirect?: string;
4
+ readonly approved?: boolean;
5
+ }): Response;
6
+ export declare function loopbackRedirect(value: string): boolean;
@@ -0,0 +1,57 @@
1
+ import { SEGMENTSTREAM_LOGO_URI } from "./logo.js";
2
+ function escape(value) {
3
+ return value.replace(/[&<>"']/g, (character) => ({
4
+ "&": "&amp;",
5
+ "<": "&lt;",
6
+ ">": "&gt;",
7
+ '"': "&quot;",
8
+ "'": "&#39;",
9
+ })[character]);
10
+ }
11
+ /** Keep a copyable PKCE-bound code visible while trying the client's loopback callback. */
12
+ export function oauthCompletion(options) {
13
+ // Web Crypto keeps this module bundleable for client components (no node: imports).
14
+ const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(18))));
15
+ const redirect = options.redirect;
16
+ const code = redirect === undefined ? null : new URL(redirect).searchParams.get("code");
17
+ const title = options.approved === false ? "Request denied" : "Authorization complete";
18
+ const manual = code === null
19
+ ? "<p>You can return to your terminal.</p>"
20
+ : `
21
+ <p>Return to your terminal. If it is still waiting, copy and paste this code:</p>
22
+ <input id="code" aria-label="Authorization code" readonly value="${escape(code)}">
23
+ <button id="copy" type="button">Copy code</button><p id="status" role="status"></p>
24
+ <p><a href="${escape(redirect)}">Continue to the client</a></p>`;
25
+ const script = code === null
26
+ ? ""
27
+ : `
28
+ document.getElementById('copy').addEventListener('click', async () => {
29
+ const input = document.getElementById('code');
30
+ try { await navigator.clipboard.writeText(input.value);
31
+ document.getElementById('status').textContent = 'Copied';
32
+ } catch { input.select(); document.getElementById('status').textContent = 'Select and copy the code'; }
33
+ });
34
+ // A frame attempts loopback without losing the fallback page on connection failure.
35
+ const frame = document.createElement('iframe'); frame.hidden = true;
36
+ frame.src = ${JSON.stringify(redirect).replace(/</g, "\\u003c")}; document.body.appendChild(frame);`;
37
+ return new Response(`<!doctype html><html lang="en"><meta charset="utf-8">
38
+ <meta name="viewport" content="width=device-width, initial-scale=1"><title>${title}</title>
39
+ <style nonce="${nonce}">body{font:16px system-ui;background:#fafafa;color:#18181b;margin:10vh auto;
40
+ max-width:480px;padding:24px}input,button{font:inherit;padding:12px;margin:8px 0}input{width:95%}</style>
41
+ <img src="${escape(SEGMENTSTREAM_LOGO_URI)}" alt="SegmentStream" width="210" height="45">
42
+ <h1>${title}</h1>${manual}<script nonce="${nonce}">${script}</script></html>`, {
43
+ headers: {
44
+ "content-type": "text/html; charset=utf-8",
45
+ "cache-control": "no-store",
46
+ "referrer-policy": "no-referrer",
47
+ "x-frame-options": "DENY",
48
+ "content-security-policy": `default-src 'none'; img-src data:; style-src 'nonce-${nonce}'; ` +
49
+ `script-src 'nonce-${nonce}'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'; ` +
50
+ `frame-src ${redirect === undefined ? "'none'" : new URL(redirect).origin}`,
51
+ },
52
+ });
53
+ }
54
+ export function loopbackRedirect(value) {
55
+ const url = new URL(value);
56
+ return url.protocol === "http:" && ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname);
57
+ }
package/dist/oauth.d.ts CHANGED
@@ -30,6 +30,7 @@ export interface AgentAuthorizations {
30
30
  * It takes `request`, `decision` (`approve` or `deny`) and, to allow reading only, `scope`
31
31
  * (`ontology:read`), and sends the browser back to the agent.
32
32
  */
33
+ readonly deviceRequest: (userCode: string) => Promise<AgentAuthorizationRequest | null>;
33
34
  readonly consentHandler: () => {
34
35
  readonly POST: (request: Request) => Promise<Response>;
35
36
  };
package/dist/oauth.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { isSameOrigin } from "./decision-pages.js";
3
3
  import { apiErrorFrom, readJson } from "./errors.js";
4
+ import { loopbackRedirect, oauthCompletion } from "./oauth-completion.js";
4
5
  import { CONSENT_PAGE } from "./pages.js";
5
6
  export { CONSENT_PAGE };
6
7
  function parseRequest(payload) {
@@ -55,6 +56,21 @@ export function createAgentAuthorizations(transport) {
55
56
  }
56
57
  return parseRequest(payload);
57
58
  },
59
+ async deviceRequest(userCode) {
60
+ const response = await transport.fetch("/oauth/device/lookup", {
61
+ method: "POST",
62
+ headers: { "content-type": "application/json" },
63
+ body: JSON.stringify({ user_code: userCode }),
64
+ });
65
+ const payload = await readJson(response);
66
+ if (response.status === 404) {
67
+ return null;
68
+ }
69
+ if (!response.ok) {
70
+ throw apiErrorFrom(response.status, payload, "The verification attempt was refused.");
71
+ }
72
+ return parseRequest(payload);
73
+ },
58
74
  consentHandler: () => ({
59
75
  async POST(request) {
60
76
  if (!isSameOrigin(request)) {
@@ -62,11 +78,21 @@ export function createAgentAuthorizations(transport) {
62
78
  }
63
79
  const form = await request.formData();
64
80
  const id = String(form.get("request") ?? "");
81
+ if (form.get("decision") === "lookup") {
82
+ const url = new URL(CONSENT_PAGE, request.url);
83
+ url.searchParams.set("device", "1");
84
+ url.searchParams.set("user_code", String(form.get("user_code") ?? "").slice(0, 32));
85
+ return NextResponse.redirect(url, 303);
86
+ }
87
+ const device = id.startsWith("device:");
65
88
  const decision = form.get("decision") === "approve" ? "approve" : "deny";
66
- const response = await transport.fetch(requestPath(id, decision), {
89
+ const response = await transport.fetch(device ? `/oauth/device/${decision}` : requestPath(id, decision), {
67
90
  method: "POST",
68
91
  headers: { "content-type": "application/json", accept: "application/json" },
69
- body: JSON.stringify(form.get("scope") === "ontology:read" ? { scope: "ontology:read" } : {}),
92
+ body: JSON.stringify({
93
+ ...(device ? { user_code: id.slice(7) } : {}),
94
+ ...(form.get("scope") === "ontology:read" ? { scope: "ontology:read" } : {}),
95
+ }),
70
96
  });
71
97
  const payload = await readJson(response);
72
98
  if (response.status === 404) {
@@ -75,10 +101,16 @@ export function createAgentAuthorizations(transport) {
75
101
  if (!response.ok) {
76
102
  throw apiErrorFrom(response.status, payload, "The ontology service refused the decision.");
77
103
  }
104
+ if (device) {
105
+ return oauthCompletion({ approved: decision === "approve" });
106
+ }
78
107
  const redirect = payload?.redirect;
79
108
  if (typeof redirect !== "string" || !URL.canParse(redirect)) {
80
109
  throw apiErrorFrom(502, payload, "The ontology service answered without a redirect.");
81
110
  }
111
+ if (decision === "approve" && loopbackRedirect(redirect)) {
112
+ return oauthCompletion({ redirect });
113
+ }
82
114
  // 303: the agent's redirect URI receives a GET, whatever method brought the browser here.
83
115
  return NextResponse.redirect(redirect, 303);
84
116
  },
@@ -13,3 +13,4 @@ export { isAllowed } from "../permissions.js";
13
13
  export type { CurrentUser } from "../me.js";
14
14
  export type { AgentPairing } from "../agent.js";
15
15
  export { SegmentStreamApiError, NOT_INVITED, SIGN_IN_LINK_INVALID } from "../errors.js";
16
+ export { SegmentStreamLogo } from "./segmentstream-logo.js";
package/dist/ui/index.js CHANGED
@@ -9,3 +9,4 @@ export { SignIn } from "./sign-in.js";
9
9
  export { SIGN_IN_ERROR_MESSAGES, signInErrorCode, signInErrorMessage, useSignIn, } from "./use-sign-in.js";
10
10
  export { isAllowed } from "../permissions.js";
11
11
  export { SegmentStreamApiError, NOT_INVITED, SIGN_IN_LINK_INVALID } from "../errors.js";
12
+ export { SegmentStreamLogo } from "./segmentstream-logo.js";
@@ -0,0 +1,2 @@
1
+ /** The official SegmentStream wordmark, also used on segmentstream.com. */
2
+ export declare function SegmentStreamLogo(): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Image from "next/image";
3
+ import { SEGMENTSTREAM_LOGO_URI } from "../logo.js";
4
+ /** The official SegmentStream wordmark, also used on segmentstream.com. */
5
+ export function SegmentStreamLogo() {
6
+ return (_jsx(Image, { unoptimized: true, src: SEGMENTSTREAM_LOGO_URI, alt: "SegmentStream", width: 210, height: 45 }));
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segmentstream-cloud/app",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "Sign-in, users, permissions, the ontology client and warehouse data access for SegmentStream workspace apps built with Next.js.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "SegmentStream",