Skip to content

Conversation

@scottlovegrove
Copy link
Contributor

@scottlovegrove scottlovegrove commented Nov 7, 2025

Summary

Adds support for custom fetch implementations to enable usage in restrictive environments like Obsidian plugins, browser extensions, Electron apps, React Native, and enterprise environments with specific networking requirements.

This PR addresses issue #381 by providing a complete custom HTTP client abstraction while maintaining 100% backward compatibility.

Key Features

  • CustomFetch and CustomFetchResponse types for HTTP client abstraction
  • Backward-compatible TodoistApi constructor with new options object pattern
  • OAuth functions support custom fetch with consistent API design
  • Complete HTTP layer integration with retry logic and error handling preserved
  • File upload support with custom fetch implementations
  • All existing transforms preserved (snake_case ↔ camelCase work automatically)
  • Comprehensive test coverage and documentation
  • Obsidian integration tests demonstrating custom fetch working with Obsidian's requestUrl API

Usage Examples

TodoistApi with Custom Fetch

// New options-based constructor
const api = new TodoistApi('TOKEN', {
    baseUrl: 'https://custom-api.example.com', // optional
    customFetch: myCustomFetch, // your custom implementation
})

// Legacy constructor (deprecated but supported)
const apiLegacy = new TodoistApi('TOKEN', 'https://custom-api.example.com')

OAuth with Custom Fetch

// New options-based usage
const { accessToken } = await getAuthToken(args, {
    baseUrl: 'https://custom-auth.example.com',
    customFetch: myCustomFetch
})

await revokeToken(args, {
    customFetch: myCustomFetch
})

// Legacy usage (deprecated)
const { accessToken } = await getAuthToken(args, baseUrl)

Custom Fetch Interface

type CustomFetch = (
    url: string,
    options?: RequestInit & { timeout?: number },
) => Promise<CustomFetchResponse>

type CustomFetchResponse = {
    ok: boolean
    status: number
    statusText: string
    headers: Record<string, string>
    text(): Promise<string>
    json(): Promise<unknown>
}

Test Coverage

  • ✅ All existing tests pass (268 tests)
  • ✅ New custom fetch functionality tests added
  • Obsidian requestUrl integration tests - 7 test cases demonstrating custom fetch working with Obsidian's API (GET, POST, DELETE requests with various parameter types)
  • ✅ TypeScript compilation (CommonJS + ESM)
  • ✅ ESLint and Prettier checks
  • ✅ Build process verification

Backward Compatibility

  • All existing APIs continue to work without changes
  • Deprecation warnings guide users to new options-based patterns
  • No breaking changes to existing functionality

Cross-Platform Benefits

This enables the Todoist API client to work in:

  • Obsidian plugins - Desktop app with strict CORS policies
  • Browser extensions - Custom HTTP APIs with different security models
  • Electron apps - Requests routed through IPC layer
  • React Native - Different networking stack
  • Enterprise environments - Proxy configuration, custom headers, certificate handling

Resolves #381

🤖 Generated with Claude Code

Adds support for custom fetch implementations to enable usage in restrictive
environments like Obsidian plugins, browser extensions, Electron apps, React
Native, and enterprise environments with specific networking requirements.

Key features:
- CustomFetch and CustomFetchResponse types for HTTP client abstraction
- Backward-compatible TodoistApi constructor with options object pattern
- OAuth functions (getAuthToken, revokeAuthToken, revokeToken) support custom fetch
- Complete HTTP layer integration with retry logic and error handling preserved
- File upload support with custom fetch implementations
- All existing transforms (snake_case ↔ camelCase) work automatically
- Comprehensive documentation and usage examples

Resolves #381

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
scottlovegrove and others added 2 commits November 8, 2025 11:55
After rebasing onto main, the test file needed to be updated to work
with MSW (Mock Service Worker) which replaced the old jest.fn() mocking
approach. Updated the test to:
- Import types from index file instead of direct imports
- Import MSW utilities from test-utils/msw-setup
- Use proper MSW handlers for mocking endpoints
- Include complete CurrentUser mock data to satisfy validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Adds comprehensive test suite demonstrating custom fetch working with
Obsidian's requestUrl API, addressing issue #381.

Changes:
- Install obsidian package as devDependency (not shipped to production)
- Create createObsidianFetchAdapter helper that bridges between:
  * Obsidian's property-based response (response.json, response.text)
  * SDK's method-based interface (response.json(), response.text())
- Add obsidian-custom-fetch.test.ts with 7 test cases covering:
  * GET requests (simple, with path params, with query params)
  * POST requests (with body, with path params and body)
  * DELETE requests (204 no-content responses)
  * Error handling

The adapter handles key differences:
- Sets throw: false to handle HTTP errors manually
- Maps Obsidian's response format to CustomFetchResponse interface
- Provides empty statusText (not available in Obsidian)
- Wraps direct properties as async methods

This demonstrates the custom fetch feature works in restrictive
environments like Obsidian plugins where standard fetch is blocked
by CORS policies.

Resolves #381

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@scottlovegrove scottlovegrove merged commit e5f13e5 into main Nov 8, 2025
1 check passed
@scottlovegrove scottlovegrove deleted the scottl/custom-fetch branch November 8, 2025 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add ability to pass custom fetch to api

2 participants