@echoxyz/sonar-react 0.0.3 → 0.1.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/CHANGELOG.md CHANGED
@@ -1,9 +1,28 @@
1
1
  # @echoxyz/sonar-react
2
2
 
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 95d0235: Include dist in build output for publishing
8
+ - Updated dependencies [95d0235]
9
+ - @echoxyz/sonar-core@0.1.1
10
+
11
+ ## 0.1.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 41247e9: Initial implementation of core/react wrapper.
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [41247e9]
20
+ - @echoxyz/sonar-core@0.1.0
21
+
3
22
  ## 0.0.3
4
23
 
5
24
  ### Patch Changes
6
25
 
7
26
  - 52ddd3f: Initial commit (empty)
8
27
  - Updated dependencies [52ddd3f]
9
- - @echoxyz/sonar-core@0.0.3
28
+ - @echoxyz/sonar-core@0.0.3
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # @echoxyz/sonar-react
2
+
3
+ React provider and hooks for Echo’s Sonar APIs, built on `@echoxyz/sonar-core`.
4
+
5
+ - Framework/router agnostic (works with React Router, Next.js, etc.).
6
+ - Handles PKCE OAuth redirect flow and token storage for the browser.
7
+ - Exposes a ready-to-use API client bound to a single `saleUUID`.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @echoxyz/sonar-react @echoxyz/sonar-core
13
+ ```
14
+
15
+ Peer dependency: `react@>=18`.
16
+
17
+ ## Quick start
18
+
19
+ 1. Wrap your app with `SonarProvider`:
20
+
21
+ ```tsx
22
+ import { SonarProvider } from "@echoxyz/sonar-react";
23
+
24
+ export function AppRoot({ children }: { children: React.ReactNode }) {
25
+ return (
26
+ <SonarProvider
27
+ config={{
28
+ saleUUID: "<your-sale-uuid>",
29
+ clientUUID: "<your-oauth-client-id>",
30
+ redirectURI: window.location.origin + "/oauth/callback",
31
+ // Optional:
32
+ // apiURL: "https://api.echo.xyz",
33
+ // tokenStorageKey: "sonar:auth-token",
34
+ }}
35
+ >
36
+ {children}
37
+ </SonarProvider>
38
+ );
39
+ }
40
+ ```
41
+
42
+ 2. Trigger login with PKCE + redirect:
43
+
44
+ ```tsx
45
+ import { useSonarAuth } from "@echoxyz/sonar-react";
46
+
47
+ export function LoginButton() {
48
+ const { login, authenticated } = useSonarAuth();
49
+ if (authenticated()) {
50
+ return null;
51
+ }
52
+ return <button onClick={() => login()}>Sign in with Echo</button>;
53
+ }
54
+ ```
55
+
56
+ 3. Complete OAuth on your callback route/page:
57
+
58
+ ```tsx
59
+ import { useEffect } from "react";
60
+ import { useSonarAuth } from "@echoxyz/sonar-react";
61
+
62
+ export default function OAuthCallback() {
63
+ const { completeOAuth } = useSonarAuth();
64
+
65
+ useEffect(() => {
66
+ const params = new URLSearchParams(window.location.search);
67
+ const code = params.get("code");
68
+ const state = params.get("state");
69
+ if (!code || !state) {
70
+ return;
71
+ }
72
+ completeOAuth({ code, state }).catch((err) => {
73
+ console.error("OAuth completion failed", err);
74
+ });
75
+ }, [completeOAuth]);
76
+
77
+ return <p>Completing sign-in…</p>;
78
+ }
79
+ ```
80
+
81
+ 4. Call APIs using the low-level client:
82
+
83
+ ```tsx
84
+ import { useEffect } from "react";
85
+ import { useSonarAuth, useSonarClient } from "@echoxyz/sonar-react";
86
+ import { EntityType } from "@echoxyz/sonar-core";
87
+
88
+ export function Example() {
89
+ const { authenticated } = useSonarAuth();
90
+ const client = useSonarClient();
91
+
92
+ useEffect(() => {
93
+ if (!authenticated()) {
94
+ return;
95
+ }
96
+
97
+ (async () => {
98
+ const { Entities } = await client.listAvailableEntities({ saleUUID: "<your-sale-uuid>" });
99
+ if (Entities.length === 0) return;
100
+
101
+ const entity = Entities[0];
102
+ const pre = await client.prePurchaseCheck({
103
+ saleUUID: "<your-sale-uuid>",
104
+ entityUUID: entity.EntityUUID,
105
+ entityType: EntityType.USER,
106
+ walletAddress: "0x1234...abcd" as `0x${string}`,
107
+ });
108
+
109
+ if (pre.ReadyToPurchase) {
110
+ const permit = await client.generatePurchasePermit({
111
+ saleUUID: "<your-sale-uuid>",
112
+ entityUUID: entity.EntityUUID,
113
+ entityType: EntityType.USER,
114
+ walletAddress: "0x1234...abcd" as `0x${string}`,
115
+ });
116
+ console.log(permit.Signature, permit.Permit);
117
+ }
118
+
119
+ const alloc = await client.fetchAllocation({
120
+ saleUUID: "<your-sale-uuid>",
121
+ walletAddress: "0x1234...abcd" as `0x${string}`,
122
+ });
123
+ console.log(alloc);
124
+ })();
125
+ }, [authenticated, client]);
126
+
127
+ return null;
128
+ }
129
+ ```
130
+
131
+ ## API
132
+
133
+ - `SonarProvider`
134
+ - Props `config`:
135
+ - `saleUUID: string` (required) – Sale to scope API calls against.
136
+ - `clientUUID: string` (required) – Echo OAuth Client ID.
137
+ - `redirectURI: string` (required) – Your OAuth callback URI.
138
+ - `apiURL?: string` (default: `https://api.echo.xyz`) – API base URL.
139
+ - `tokenStorageKey?: string` (default: `sonar:auth-token`) – Browser storage key for the access token.
140
+
141
+ - `useSonarAuth()` → `{ authenticated, token?, login(), completeOAuth({ code, state }), logout() }`
142
+
143
+ - `useSonarClient()` → low-level `SonarClient` instance.
144
+
145
+ ## Notes
146
+
147
+ - Tokens are not auto-refreshed. On expiry, call `logout()` and re-run the OAuth flow.
148
+ - This package doesn’t depend on a specific router. Use it in Next.js, React Router, or any custom setup.
149
+ - Wallet addresses are typed as template literals `0x${string}`.
package/dist/index.cjs ADDED
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.tsx
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ SonarProvider: () => SonarProvider,
24
+ useSonarAuth: () => useSonarAuth,
25
+ useSonarClient: () => useSonarClient
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/react.tsx
30
+ var import_sonar_core = require("@echoxyz/sonar-core");
31
+ var import_react = require("react");
32
+ var import_jsx_runtime = require("react/jsx-runtime");
33
+ var AuthContext = (0, import_react.createContext)(void 0);
34
+ var ClientContext = (0, import_react.createContext)(void 0);
35
+ function SonarProvider({ children, config }) {
36
+ const client = (0, import_react.useMemo)(() => {
37
+ return (0, import_sonar_core.createClient)({
38
+ saleUUID: config.saleUUID,
39
+ apiURL: config.apiURL,
40
+ tokenKey: config.tokenStorageKey
41
+ });
42
+ }, [config.apiURL, config.saleUUID, config.tokenStorageKey]);
43
+ const login = (0, import_react.useCallback)(async () => {
44
+ const { codeVerifier, codeChallenge, state } = await (0, import_sonar_core.generatePKCEParams)();
45
+ if (typeof window === "undefined") {
46
+ throw new Error("window is not available for OAuth flow");
47
+ }
48
+ window.sessionStorage.setItem("sonar:oauth:state", state);
49
+ window.sessionStorage.setItem("sonar:oauth:verifier", codeVerifier);
50
+ const url = (0, import_sonar_core.buildAuthorizationUrl)({
51
+ saleUUID: config.saleUUID,
52
+ clientUUID: config.clientUUID,
53
+ redirectURI: config.redirectURI,
54
+ state,
55
+ codeChallenge,
56
+ frontendURL: config.frontendURL
57
+ });
58
+ window.location.href = url.toString();
59
+ }, [config.saleUUID, config.clientUUID, config.redirectURI, config.frontendURL]);
60
+ const completeOAuth = (0, import_react.useCallback)(
61
+ async ({ code, state }) => {
62
+ if (typeof window === "undefined") {
63
+ throw new Error("window is not available for OAuth verification");
64
+ }
65
+ const expectedState = window.sessionStorage.getItem("sonar:oauth:state");
66
+ const codeVerifier = window.sessionStorage.getItem("sonar:oauth:verifier");
67
+ if (state !== expectedState || !codeVerifier) {
68
+ throw new Error("Invalid OAuth state or missing verifier");
69
+ }
70
+ const { token } = await client.exchangeAuthorizationCode({
71
+ code,
72
+ codeVerifier,
73
+ redirectURI: config.redirectURI
74
+ });
75
+ client.setToken(token);
76
+ window.sessionStorage.removeItem("sonar:oauth:state");
77
+ window.sessionStorage.removeItem("sonar:oauth:verifier");
78
+ },
79
+ [client, config.redirectURI]
80
+ );
81
+ const logout = (0, import_react.useCallback)(() => {
82
+ client.clear();
83
+ }, []);
84
+ const authValue = (0, import_react.useMemo)(
85
+ () => ({
86
+ login,
87
+ logout,
88
+ authenticated: () => Boolean(client.getToken()),
89
+ token: client.getToken() ?? void 0,
90
+ completeOAuth
91
+ }),
92
+ [client, login, completeOAuth, logout]
93
+ );
94
+ const clientValue = (0, import_react.useMemo)(() => ({ client }), [client]);
95
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext.Provider, { value: authValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ClientContext.Provider, { value: clientValue, children }) });
96
+ }
97
+ function useSonarAuth() {
98
+ const ctx = (0, import_react.useContext)(AuthContext);
99
+ if (!ctx) {
100
+ throw new Error("useSonarAuth must be used within a SonarProvider");
101
+ }
102
+ return ctx;
103
+ }
104
+ function useSonarClient() {
105
+ const ctx = (0, import_react.useContext)(ClientContext);
106
+ if (!ctx) {
107
+ throw new Error("useSonarClient must be used within a SonarProvider");
108
+ }
109
+ return ctx.client;
110
+ }
111
+ // Annotate the CommonJS export names for ESM import in node:
112
+ 0 && (module.exports = {
113
+ SonarProvider,
114
+ useSonarAuth,
115
+ useSonarClient
116
+ });
@@ -0,0 +1,30 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { SonarClient } from '@echoxyz/sonar-core';
3
+ import React from 'react';
4
+
5
+ type SonarProviderProps = {
6
+ children: React.ReactNode;
7
+ config: {
8
+ saleUUID: string;
9
+ clientUUID: string;
10
+ redirectURI: string;
11
+ apiURL?: string;
12
+ frontendURL?: string;
13
+ tokenStorageKey?: string;
14
+ };
15
+ };
16
+ type AuthContextValue = {
17
+ authenticated: () => boolean;
18
+ token?: string;
19
+ login: () => Promise<void>;
20
+ completeOAuth: (args: {
21
+ code: string;
22
+ state: string;
23
+ }) => Promise<void>;
24
+ logout: () => void;
25
+ };
26
+ declare function SonarProvider({ children, config }: SonarProviderProps): react_jsx_runtime.JSX.Element;
27
+ declare function useSonarAuth(): AuthContextValue;
28
+ declare function useSonarClient(): SonarClient;
29
+
30
+ export { SonarProvider, useSonarAuth, useSonarClient };
@@ -0,0 +1,30 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { SonarClient } from '@echoxyz/sonar-core';
3
+ import React from 'react';
4
+
5
+ type SonarProviderProps = {
6
+ children: React.ReactNode;
7
+ config: {
8
+ saleUUID: string;
9
+ clientUUID: string;
10
+ redirectURI: string;
11
+ apiURL?: string;
12
+ frontendURL?: string;
13
+ tokenStorageKey?: string;
14
+ };
15
+ };
16
+ type AuthContextValue = {
17
+ authenticated: () => boolean;
18
+ token?: string;
19
+ login: () => Promise<void>;
20
+ completeOAuth: (args: {
21
+ code: string;
22
+ state: string;
23
+ }) => Promise<void>;
24
+ logout: () => void;
25
+ };
26
+ declare function SonarProvider({ children, config }: SonarProviderProps): react_jsx_runtime.JSX.Element;
27
+ declare function useSonarAuth(): AuthContextValue;
28
+ declare function useSonarClient(): SonarClient;
29
+
30
+ export { SonarProvider, useSonarAuth, useSonarClient };
package/dist/index.js ADDED
@@ -0,0 +1,87 @@
1
+ // src/react.tsx
2
+ import { buildAuthorizationUrl, createClient, generatePKCEParams } from "@echoxyz/sonar-core";
3
+ import { createContext, useCallback, useContext, useMemo } from "react";
4
+ import { jsx } from "react/jsx-runtime";
5
+ var AuthContext = createContext(void 0);
6
+ var ClientContext = createContext(void 0);
7
+ function SonarProvider({ children, config }) {
8
+ const client = useMemo(() => {
9
+ return createClient({
10
+ saleUUID: config.saleUUID,
11
+ apiURL: config.apiURL,
12
+ tokenKey: config.tokenStorageKey
13
+ });
14
+ }, [config.apiURL, config.saleUUID, config.tokenStorageKey]);
15
+ const login = useCallback(async () => {
16
+ const { codeVerifier, codeChallenge, state } = await generatePKCEParams();
17
+ if (typeof window === "undefined") {
18
+ throw new Error("window is not available for OAuth flow");
19
+ }
20
+ window.sessionStorage.setItem("sonar:oauth:state", state);
21
+ window.sessionStorage.setItem("sonar:oauth:verifier", codeVerifier);
22
+ const url = buildAuthorizationUrl({
23
+ saleUUID: config.saleUUID,
24
+ clientUUID: config.clientUUID,
25
+ redirectURI: config.redirectURI,
26
+ state,
27
+ codeChallenge,
28
+ frontendURL: config.frontendURL
29
+ });
30
+ window.location.href = url.toString();
31
+ }, [config.saleUUID, config.clientUUID, config.redirectURI, config.frontendURL]);
32
+ const completeOAuth = useCallback(
33
+ async ({ code, state }) => {
34
+ if (typeof window === "undefined") {
35
+ throw new Error("window is not available for OAuth verification");
36
+ }
37
+ const expectedState = window.sessionStorage.getItem("sonar:oauth:state");
38
+ const codeVerifier = window.sessionStorage.getItem("sonar:oauth:verifier");
39
+ if (state !== expectedState || !codeVerifier) {
40
+ throw new Error("Invalid OAuth state or missing verifier");
41
+ }
42
+ const { token } = await client.exchangeAuthorizationCode({
43
+ code,
44
+ codeVerifier,
45
+ redirectURI: config.redirectURI
46
+ });
47
+ client.setToken(token);
48
+ window.sessionStorage.removeItem("sonar:oauth:state");
49
+ window.sessionStorage.removeItem("sonar:oauth:verifier");
50
+ },
51
+ [client, config.redirectURI]
52
+ );
53
+ const logout = useCallback(() => {
54
+ client.clear();
55
+ }, []);
56
+ const authValue = useMemo(
57
+ () => ({
58
+ login,
59
+ logout,
60
+ authenticated: () => Boolean(client.getToken()),
61
+ token: client.getToken() ?? void 0,
62
+ completeOAuth
63
+ }),
64
+ [client, login, completeOAuth, logout]
65
+ );
66
+ const clientValue = useMemo(() => ({ client }), [client]);
67
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value: authValue, children: /* @__PURE__ */ jsx(ClientContext.Provider, { value: clientValue, children }) });
68
+ }
69
+ function useSonarAuth() {
70
+ const ctx = useContext(AuthContext);
71
+ if (!ctx) {
72
+ throw new Error("useSonarAuth must be used within a SonarProvider");
73
+ }
74
+ return ctx;
75
+ }
76
+ function useSonarClient() {
77
+ const ctx = useContext(ClientContext);
78
+ if (!ctx) {
79
+ throw new Error("useSonarClient must be used within a SonarProvider");
80
+ }
81
+ return ctx.client;
82
+ }
83
+ export {
84
+ SonarProvider,
85
+ useSonarAuth,
86
+ useSonarClient
87
+ };
package/package.json CHANGED
@@ -1,26 +1,27 @@
1
1
  {
2
2
  "name": "@echoxyz/sonar-react",
3
- "version": "0.0.3",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
- "module": "./dist/index.mjs",
6
+ "module": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/index.mjs",
11
- "require": "./dist/index.cjs",
12
- "types": "./dist/index.d.ts"
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
13
  }
14
14
  },
15
15
  "peerDependencies": {
16
16
  "react": ">=18"
17
17
  },
18
18
  "dependencies": {
19
- "@echoxyz/sonar-core": "0.0.3"
19
+ "@echoxyz/sonar-core": "0.1.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "tsup": "^8.0.0",
23
- "vitest": "^2.0.0"
23
+ "vitest": "^2.0.0",
24
+ "@types/react": "^18"
24
25
  },
25
26
  "repository": {
26
27
  "type": "git",
@@ -36,8 +37,8 @@
36
37
  "package.json"
37
38
  ],
38
39
  "scripts": {
39
- "build": "tsup src/index.ts --format esm,cjs --dts",
40
- "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
40
+ "build": "tsup src/index.tsx --format esm,cjs --dts",
41
+ "dev": "tsup src/index.tsx --format esm,cjs --dts --watch",
41
42
  "test": "vitest run"
42
43
  }
43
44
  }