@basictech/react 0.8.0-beta.4 → 0.9.0-beta.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.
@@ -1,153 +0,0 @@
1
- import { BasicStorage } from '../utils/storage'
2
- import { log } from '../config'
3
-
4
- export interface VersionInfo {
5
- version: string
6
- lastUpdated: number
7
- }
8
-
9
- export interface Migration {
10
- fromVersion: string
11
- toVersion: string
12
- migrate: (storage: BasicStorage) => Promise<void>
13
- }
14
-
15
- export class VersionUpdater {
16
- private storage: BasicStorage
17
- private currentVersion: string
18
- private migrations: Migration[]
19
- private versionKey = 'basic_app_version'
20
-
21
- constructor(storage: BasicStorage, currentVersion: string, migrations: Migration[] = []) {
22
- this.storage = storage
23
- this.currentVersion = currentVersion
24
- this.migrations = migrations.sort((a, b) => this.compareVersions(a.fromVersion, b.fromVersion))
25
- }
26
-
27
- /**
28
- * Check current stored version and run migrations if needed
29
- * Only compares major.minor versions, ignoring beta/prerelease parts
30
- * Example: "0.7.0-beta.1" and "0.7.0" are treated as the same version
31
- */
32
- async checkAndUpdate(): Promise<{ updated: boolean; fromVersion?: string; toVersion: string }> {
33
- const storedVersion = await this.getStoredVersion()
34
-
35
- if (!storedVersion) {
36
- // First time setup
37
- await this.setStoredVersion(this.currentVersion)
38
- return { updated: false, toVersion: this.currentVersion }
39
- }
40
-
41
- if (storedVersion === this.currentVersion) {
42
- return { updated: false, toVersion: this.currentVersion }
43
- }
44
-
45
- // Need to run migrations
46
- const migrationsToRun = this.getMigrationsToRun(storedVersion, this.currentVersion)
47
-
48
- if (migrationsToRun.length === 0) {
49
- // No migrations needed, just update version
50
- await this.setStoredVersion(this.currentVersion)
51
- return { updated: true, fromVersion: storedVersion, toVersion: this.currentVersion }
52
- }
53
-
54
- // Run migrations
55
- for (const migration of migrationsToRun) {
56
- try {
57
- log(`Running migration from ${migration.fromVersion} to ${migration.toVersion}`)
58
- await migration.migrate(this.storage)
59
- } catch (error) {
60
- console.error(`Migration failed from ${migration.fromVersion} to ${migration.toVersion}:`, error)
61
- throw new Error(`Migration failed: ${error}`)
62
- }
63
- }
64
-
65
- // Update to current version
66
- await this.setStoredVersion(this.currentVersion)
67
- return { updated: true, fromVersion: storedVersion, toVersion: this.currentVersion }
68
- }
69
-
70
- private async getStoredVersion(): Promise<string | null> {
71
- try {
72
- const versionData = await this.storage.get(this.versionKey)
73
- if (!versionData) return null
74
-
75
- const versionInfo: VersionInfo = JSON.parse(versionData)
76
- return versionInfo.version
77
- } catch (error) {
78
- console.warn('Failed to get stored version:', error)
79
- return null
80
- }
81
- }
82
-
83
- private async setStoredVersion(version: string): Promise<void> {
84
- const versionInfo: VersionInfo = {
85
- version,
86
- lastUpdated: Date.now()
87
- }
88
- await this.storage.set(this.versionKey, JSON.stringify(versionInfo))
89
- }
90
-
91
- private getMigrationsToRun(fromVersion: string, toVersion: string): Migration[] {
92
- return this.migrations.filter(migration => {
93
- const storedLessThanMigrationTo = this.compareVersions(fromVersion, migration.toVersion) < 0
94
- const currentGreaterThanOrEqualMigrationTo = this.compareVersions(toVersion, migration.toVersion) >= 0
95
- const shouldRun = storedLessThanMigrationTo && currentGreaterThanOrEqualMigrationTo
96
- log(`Migration ${migration.fromVersion} → ${migration.toVersion}: shouldRun=${shouldRun}`)
97
- return shouldRun
98
- })
99
- }
100
-
101
- /**
102
- * Simple semantic version comparison (major.minor only, ignoring beta/prerelease)
103
- * Returns: -1 if a < b, 0 if a === b, 1 if a > b
104
- */
105
- private compareVersions(a: string, b: string): number {
106
- // Extract major.minor from version strings, ignoring beta/prerelease parts
107
- const aMajorMinor = this.extractMajorMinor(a)
108
- const bMajorMinor = this.extractMajorMinor(b)
109
-
110
- // Compare major version first
111
- if (aMajorMinor.major !== bMajorMinor.major) {
112
- return aMajorMinor.major - bMajorMinor.major
113
- }
114
-
115
- // Then compare minor version
116
- return aMajorMinor.minor - bMajorMinor.minor
117
- }
118
-
119
- /**
120
- * Extract major.minor from version string, ignoring beta/prerelease
121
- * Examples: "0.7.0-beta.1" -> {major: 0, minor: 7}
122
- * "1.2.3" -> {major: 1, minor: 2}
123
- */
124
- private extractMajorMinor(version: string): { major: number, minor: number } {
125
- // Remove beta/prerelease parts and split by dots
126
- const cleanVersion = version.split('-')[0]?.split('+')[0] || version
127
- const parts = cleanVersion.split('.').map(Number)
128
-
129
- return {
130
- major: parts[0] || 0,
131
- minor: parts[1] || 0
132
- }
133
- }
134
-
135
- /**
136
- * Add a migration to the updater
137
- */
138
- addMigration(migration: Migration): void {
139
- this.migrations.push(migration)
140
- this.migrations.sort((a, b) => this.compareVersions(a.fromVersion, b.fromVersion))
141
- }
142
- }
143
-
144
- /**
145
- * Create a simple version updater instance
146
- */
147
- export function createVersionUpdater(
148
- storage: BasicStorage,
149
- currentVersion: string,
150
- migrations: Migration[] = []
151
- ): VersionUpdater {
152
- return new VersionUpdater(storage, currentVersion, migrations)
153
- }
@@ -1,135 +0,0 @@
1
- // Network utilities for Basic React package
2
- import semver from 'semver'
3
- import { log } from '../config'
4
- import { version as pkgVersion } from '../../package.json'
5
-
6
- export function isDevelopment(debug?: boolean): boolean {
7
- if (debug === true) return true
8
- if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') return true
9
- if (typeof window === 'undefined' || !window.location) return false
10
- const host = window.location.hostname
11
- return (
12
- host === 'localhost' ||
13
- host === '127.0.0.1' ||
14
- host.includes('localhost') ||
15
- host.includes('127.0.0.1') ||
16
- host.includes('.local')
17
- )
18
- }
19
-
20
- function normalizeVersion(v: string | null | undefined): string | null {
21
- if (v == null) return null
22
- const t = String(v).trim()
23
- return t.length ? t : null
24
- }
25
-
26
- function versionsMatch(a: string, b: string): boolean {
27
- const na = a.trim()
28
- const nb = b.trim()
29
- if (na === nb) return true
30
- const va = semver.valid(na)
31
- const vb = semver.valid(nb)
32
- if (va && vb) return semver.eq(va, vb)
33
- return false
34
- }
35
-
36
- /** Use npm `beta` dist-tag when the installed version is a semver prerelease whose first id is `beta`. */
37
- function usesBetaDistTag(version: string): boolean {
38
- const pre = semver.prerelease(version)
39
- const id = pre?.[0]
40
- return typeof id === 'string' && id.toLowerCase() === 'beta'
41
- }
42
-
43
- type NpmInstallMeta = {
44
- 'dist-tags'?: { latest?: string; beta?: string }
45
- }
46
-
47
- export async function checkForNewVersion(): Promise<{
48
- hasNewVersion: boolean,
49
- latestVersion: string | null,
50
- currentVersion: string | null
51
- }> {
52
- try {
53
- const currentVersion = normalizeVersion(pkgVersion)
54
- if (!currentVersion) {
55
- return { hasNewVersion: false, latestVersion: null, currentVersion: null }
56
- }
57
-
58
- const response = await fetch('https://registry.npmjs.org/@basictech/react', {
59
- headers: { Accept: 'application/vnd.npm.install-v1+json' },
60
- })
61
- if (!response.ok) {
62
- throw new Error('Failed to fetch version from npm');
63
- }
64
-
65
- const data = (await response.json()) as NpmInstallMeta
66
- const distTags = data['dist-tags'] ?? {}
67
- const rawRegistry =
68
- usesBetaDistTag(currentVersion)
69
- ? distTags.beta ?? distTags.latest
70
- : distTags.latest
71
- const latestVersion = normalizeVersion(rawRegistry ?? null)
72
- if (!latestVersion) {
73
- throw new Error('Missing dist-tags from npm registry')
74
- }
75
-
76
- const same = versionsMatch(currentVersion, latestVersion)
77
-
78
- if (!same && isDevelopment()) {
79
- log('[basic] version check mismatch:', {
80
- currentVersion,
81
- registryVersion: latestVersion,
82
- channel: usesBetaDistTag(currentVersion) ? 'beta' : 'latest',
83
- })
84
- }
85
-
86
- if (!same) {
87
- console.warn('[basic] New version available:', latestVersion, `\nrun "npm install @basictech/react@${latestVersion}" to update`);
88
- }
89
- if (usesBetaDistTag(currentVersion)) {
90
- log('thank you for being on basictech/react beta :)')
91
- }
92
-
93
- return {
94
- hasNewVersion: !same,
95
- latestVersion,
96
- currentVersion
97
- };
98
- } catch (error) {
99
- log('Error checking for new version:', error);
100
- return {
101
- hasNewVersion: false,
102
- latestVersion: null,
103
- currentVersion: null
104
- };
105
- }
106
- }
107
-
108
- export function cleanOAuthParamsFromUrl(): void {
109
- if (window.location.search.includes('code') || window.location.search.includes('state')) {
110
- const url = new URL(window.location.href)
111
- url.searchParams.delete('code')
112
- url.searchParams.delete('state')
113
- window.history.replaceState({}, document.title, url.pathname + url.search)
114
- log('Cleaned OAuth parameters from URL')
115
- }
116
- }
117
-
118
- export function getSyncStatus(statusCode: number): string {
119
- switch (statusCode) {
120
- case -1:
121
- return "ERROR";
122
- case 0:
123
- return "OFFLINE";
124
- case 1:
125
- return "CONNECTING";
126
- case 2:
127
- return "ONLINE";
128
- case 3:
129
- return "SYNCING";
130
- case 4:
131
- return "ERROR_WILL_RETRY";
132
- default:
133
- return "UNKNOWN";
134
- }
135
- }
@@ -1,22 +0,0 @@
1
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
2
-
3
- /**
4
- * Normalize a project_id / client_id to the canonical did:web format
5
- * before sending it to the PDS.
6
- *
7
- * - did:web:... -> passthrough (already canonical)
8
- * - bare UUID -> did:web:{adminHostname}:projects:{hex}
9
- * - "self" -> passthrough
10
- */
11
- export function normalizeClientId(projectId: string, adminHostname: string = 'api.basic.tech'): string {
12
- if (!projectId) return projectId
13
- if (projectId === 'self') return projectId
14
- if (projectId.startsWith('did:')) return projectId
15
-
16
- if (UUID_RE.test(projectId)) {
17
- const hex = projectId.replace(/-/g, '').toLowerCase()
18
- return `did:web:${adminHostname}:projects:${hex}`
19
- }
20
-
21
- return projectId
22
- }
@@ -1,101 +0,0 @@
1
- export type ResolvedDid = {
2
- did: string
3
- handle?: string
4
- didDocument: Record<string, unknown>
5
- pdsUrl: string
6
- authorization_endpoint: string
7
- token_endpoint: string
8
- userinfo_endpoint: string
9
- }
10
-
11
- /**
12
- * Convert a did:web DID to the HTTPS URL where its DID document lives.
13
- *
14
- * did:web:pds.basic.id:did:abc123 -> https://pds.basic.id/did/abc123/did.json
15
- * did:web:example.com -> https://example.com/.well-known/did.json
16
- */
17
- export function resolveDidWebUrl(did: string): string | null {
18
- if (!did.startsWith('did:web:')) return null
19
-
20
- const rest = did.slice(8) // strip 'did:web:'
21
- if (!rest) return null
22
- const parts = rest.split(':')
23
-
24
- // Decode the hostname (first part, may contain %3A for port)
25
- const hostname = parts[0]!.replace(/%3A/gi, ':')
26
-
27
- if (parts.length === 1) {
28
- return `https://${hostname}/.well-known/did.json`
29
- }
30
-
31
- const pathParts = parts.slice(1).map(p => decodeURIComponent(p))
32
- return `https://${hostname}/${pathParts.join('/')}/did.json`
33
- }
34
-
35
- /**
36
- * Given a DID document, extract PDS URL and discover OAuth endpoints.
37
- */
38
- async function resolveFromDocument(did: string, didDocument: Record<string, unknown>): Promise<ResolvedDid> {
39
- const services = didDocument.service as Array<{ id: string; type: string; serviceEndpoint: string }> | undefined
40
- const pdsService = services?.find(
41
- (s) => s.id === '#basic_pds' || s.id === `${did}#basic_pds`
42
- )
43
- if (!pdsService) {
44
- throw new Error(`DID document has no #basic_pds service entry`)
45
- }
46
- const pdsUrl = pdsService.serviceEndpoint.replace(/\/+$/, '')
47
-
48
- const oauthRes = await fetch(`${pdsUrl}/auth/.well-known/openid-configuration`)
49
- if (!oauthRes.ok) {
50
- throw new Error(`Failed to fetch OpenID configuration from ${pdsUrl}: ${oauthRes.status}`)
51
- }
52
- const oauth = await oauthRes.json()
53
-
54
- return {
55
- did,
56
- didDocument,
57
- pdsUrl,
58
- authorization_endpoint: oauth.authorization_endpoint,
59
- token_endpoint: oauth.token_endpoint,
60
- userinfo_endpoint: oauth.userinfo_endpoint,
61
- }
62
- }
63
-
64
- /**
65
- * Fetch a DID document by DID, extract the PDS URL, and discover OAuth endpoints.
66
- */
67
- export async function resolveDid(did: string): Promise<ResolvedDid> {
68
- const url = resolveDidWebUrl(did)
69
- if (!url) {
70
- throw new Error(`Unsupported DID method: ${did}`)
71
- }
72
-
73
- const didRes = await fetch(url)
74
- if (!didRes.ok) {
75
- throw new Error(`Failed to fetch DID document at ${url}: ${didRes.status}`)
76
- }
77
- const didDocument = await didRes.json()
78
-
79
- return resolveFromDocument(did, didDocument)
80
- }
81
-
82
- /**
83
- * Resolve a handle (e.g. "alice.basic.id") to a DID and discover PDS + OAuth endpoints.
84
- *
85
- * Fetches https://{handle}/.well-known/did.json per the did:web spec.
86
- */
87
- export async function resolveHandle(handle: string): Promise<ResolvedDid> {
88
- const res = await fetch(`https://${handle}/.well-known/did.json`)
89
- if (!res.ok) {
90
- throw new Error(`Handle resolution failed for ${handle}: ${res.status}`)
91
- }
92
- const didDocument = await res.json()
93
- const did = didDocument.id as string
94
- if (!did) {
95
- throw new Error(`Handle response has no 'id' field`)
96
- }
97
-
98
- const resolved = await resolveFromDocument(did, didDocument)
99
- resolved.handle = handle
100
- return resolved
101
- }
@@ -1,119 +0,0 @@
1
- // Schema utilities for Basic React package
2
- import { validateSchema, compareSchemas } from '@basictech/schema'
3
- import { log } from '../config'
4
-
5
- export async function getSchemaStatus(schema: any): Promise<{
6
- valid: boolean,
7
- status: string,
8
- latest: any
9
- }> {
10
- const projectId = schema.project_id
11
- const valid = validateSchema(schema)
12
-
13
- if (!valid.valid) {
14
- console.warn('BasicDB Error: your local schema is invalid. Please fix errors and try again - sync is disabled')
15
- return {
16
- valid: false,
17
- status: 'invalid',
18
- latest: null
19
- }
20
- }
21
-
22
- const latestSchema = await fetch(`https://api.basic.tech/project/${projectId}/schema`)
23
- .then(res => res.json())
24
- .then(data => data.data[0].schema)
25
- .catch(err => {
26
- return {
27
- valid: false,
28
- status: 'error',
29
- latest: null
30
- }
31
- })
32
-
33
- if (!latestSchema.version) {
34
- return {
35
- valid: false,
36
- status: 'error',
37
- latest: null
38
- }
39
- }
40
-
41
- if (latestSchema.version > schema.version) {
42
- // error_code: schema_behind
43
- console.warn('BasicDB Error: your local schema version is behind the latest. Found version:', schema.version, 'but expected', latestSchema.version, " - sync is disabled")
44
- return {
45
- valid: false,
46
- status: 'behind',
47
- latest: latestSchema
48
- }
49
- } else if (latestSchema.version < schema.version) {
50
- // error_code: schema_ahead
51
- console.warn('BasicDB Error: your local schema version is ahead of the latest. Found version:', schema.version, 'but expected', latestSchema.version, " - sync is disabled")
52
- return {
53
- valid: false,
54
- status: 'ahead',
55
- latest: latestSchema
56
- }
57
- } else if (latestSchema.version === schema.version) {
58
- const changes = compareSchemas(schema, latestSchema)
59
- if (changes.valid) {
60
- return {
61
- valid: true,
62
- status: 'current',
63
- latest: latestSchema
64
- }
65
- } else {
66
- // error_code: schema_conflict
67
- console.warn('BasicDB Error: your local schema is conflicting with the latest. Your version:', schema.version, 'does not match origin version', latestSchema.version, " - sync is disabled")
68
- return {
69
- valid: false,
70
- status: 'conflict',
71
- latest: latestSchema
72
- }
73
- }
74
- } else {
75
- return {
76
- valid: false,
77
- status: 'error',
78
- latest: null
79
- }
80
- }
81
- }
82
-
83
- export async function validateAndCheckSchema(schema: any): Promise<{
84
- isValid: boolean,
85
- schemaStatus: { valid: boolean, status?: string, latest?: any },
86
- errors?: any[]
87
- }> {
88
- const valid = validateSchema(schema)
89
- if (!valid.valid) {
90
- log('Basic Schema is invalid!', valid.errors)
91
- console.group('Schema Errors')
92
- let errorMessage = ''
93
- valid.errors.forEach((error, index) => {
94
- log(`${index + 1}:`, error.message, ` - at ${error.instancePath}`)
95
- errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}\n`
96
- })
97
- console.groupEnd()
98
-
99
- return {
100
- isValid: false,
101
- schemaStatus: { valid: false },
102
- errors: valid.errors
103
- }
104
- }
105
-
106
- let schemaStatus: { valid: boolean, status?: string, latest?: any } = { valid: false }
107
- if (schema.version !== 0) {
108
- schemaStatus = await getSchemaStatus(schema)
109
- log('schemaStatus', schemaStatus)
110
- } else {
111
- schemaStatus = { valid: false, status: 'unpublished' }
112
- log("schema not published - at version 0")
113
- }
114
-
115
- return {
116
- isValid: true,
117
- schemaStatus
118
- }
119
- }
@@ -1,67 +0,0 @@
1
- // Storage utilities for Basic React package
2
- export interface BasicStorage {
3
- get(key: string): Promise<string | null>
4
- set(key: string, value: string): Promise<void>
5
- remove(key: string): Promise<void>
6
- }
7
-
8
- export class LocalStorageAdapter implements BasicStorage {
9
- async get(key: string): Promise<string | null> {
10
- return localStorage.getItem(key)
11
- }
12
-
13
- async set(key: string, value: string): Promise<void> {
14
- localStorage.setItem(key, value)
15
- }
16
-
17
- async remove(key: string): Promise<void> {
18
- localStorage.removeItem(key)
19
- }
20
- }
21
-
22
- export const STORAGE_KEYS = {
23
- REFRESH_TOKEN: 'basic_refresh_token',
24
- USER_INFO: 'basic_user_info',
25
- AUTH_STATE: 'basic_auth_state',
26
- REDIRECT_URI: 'basic_redirect_uri',
27
- SERVER_URL: 'basic_server_url',
28
- PDS_ENDPOINTS: 'basic_pds_endpoints',
29
- LAST_CONNECT_REPORT: 'basic_last_connect_report',
30
- DEBUG: 'basic_debug',
31
- CODE_VERIFIER: 'basic_code_verifier'
32
- } as const
33
-
34
- export function getCookie(name: string): string {
35
- let cookieValue = '';
36
- if (document.cookie && document.cookie !== '') {
37
- const cookies = document.cookie.split(';');
38
- for (let i = 0; i < cookies.length; i++) {
39
- const cookie = cookies[i]?.trim();
40
- if (cookie && cookie.substring(0, name.length + 1) === (name + '=')) {
41
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
42
- break;
43
- }
44
- }
45
- }
46
- return cookieValue;
47
- }
48
-
49
- export function setCookie(name: string, value: string, options?: { secure?: boolean, sameSite?: string, httpOnly?: boolean }): void {
50
- const opts = {
51
- secure: true,
52
- sameSite: 'Strict',
53
- httpOnly: false,
54
- ...options
55
- };
56
-
57
- let cookieString = `${name}=${value}`;
58
- if (opts.secure) cookieString += '; Secure';
59
- if (opts.sameSite) cookieString += `; SameSite=${opts.sameSite}`;
60
- if (opts.httpOnly) cookieString += '; HttpOnly';
61
-
62
- document.cookie = cookieString;
63
- }
64
-
65
- export function clearCookie(name: string): void {
66
- document.cookie = `${name}=; Secure; SameSite=Strict`;
67
- }
package/tsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "@repo/typescript-config/react-library.json",
3
- "compilerOptions": {
4
- "outDir": "dist"
5
- },
6
- "include": ["src"],
7
- "exclude": ["node_modules", "dist"]
8
- }
9
-
package/tsup.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import { defineConfig } from 'tsup'
2
-
3
- export default defineConfig({
4
- entry: ['src/index.ts'],
5
- format: ['cjs', 'esm'],
6
- dts: true,
7
- splitting: false,
8
- sourcemap: true,
9
- clean: true,
10
- noExternal: ['@repo/sync']
11
- })