Skip to content

feat: migrate SDK to Quran Foundation Content APIs with OAuth2 support #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
node_modules
dist
coverage
.husky
.husky
.env
39 changes: 28 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
![Quranjs Api Header](https://github.com/quran/api-js/raw/master/media/repo-header.png)
A library for fetching quran data from the [quran.com API][qdc-api]. This library also works on both Node.js and the browser.

[Checkout Docs][docs]
# Quran Foundation Content API - JavaScript SDK

This package provides a JavaScript SDK for the **Quran Foundation Content API** and works in both Node.js and the browser.

> **Full documentation is available at <https://api-docs.quran.foundation/sdk>.**

[![Build Status][build-badge]][build]
[![MIT License][license-badge]][license]
Expand All @@ -11,26 +14,40 @@ A library for fetching quran data from the [quran.com API][qdc-api]. This librar

## Installation

using npm:

```ssh
```bash
npm install @quranjs/api
```

using yarn:
or using pnpm / yarn:

```ssh
```bash
pnpm add @quranjs/api
# or
yarn add @quranjs/api
```

## Getting Started
## Quick Start

```js
import { configure, quran } from '@quranjs/api';

configure({
clientId: '<YOUR_QF_CLIENT_ID>',
clientSecret: '<YOUR_QF_CLIENT_SECRET>',
});

const chapters = await quran.qf.chapters.findAll();
console.log(chapters);
```

For more examples and a complete API reference, see the [SDK documentation](https://api-docs.quran.foundation/sdk).

## Migrating from previous versions

you can visit the [docs][docs] for more details.
If you used an earlier version of this SDK, please check the migration guide on the [documentation site](https://api-docs.quran.foundation/sdk) for details on upgrading.

<!-- Links -->

[qdc-api]: https://api-docs.quran.com/docs/category/content-apis
[docs]: https://quranjs.com/
[build-badge]: https://github.com/quran/api-js/workflows/CI/badge.svg
[build]: https://github.com/quran/api-js/actions?query=workflow%3ACI
[license-badge]: https://badgen.net/github/license/quranjs/api
Expand Down
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"access": "public"
},
"packageManager": "pnpm@9.12.0",
"version": "1.7.1",
"version": "2.0.0",
"license": "MIT",
"main": "dist/index.min.js",
"module": "dist/index.min.mjs",
Expand Down Expand Up @@ -36,16 +36,21 @@
"analyze": "size-limit --why"
},
"dependencies": {
"async-retry": "^1.3.3",
"cross-fetch": "^3.1.5",
"dayjs": "^1.11.11",
"dotenv": "^17.0.1",
"humps": "^2.0.1"
},
"devDependencies": {
"@size-limit/preset-small-lib": "^7.0.8",
"@swc/core": "^1.10.4",
"@types/async-retry": "^1.4.9",
"@types/humps": "^2.0.1",
"@types/node": "^24.0.10",
"@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.42.0",
"@vitest/coverage-c8": "^0.24.4",
"cross-fetch": "^3.1.5",
"esbuild-plugin-umd-wrapper": "^3.0.0",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
Expand Down Expand Up @@ -78,4 +83,4 @@
"engines": {
"node": ">=12"
}
}
}
74 changes: 66 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions src/auth/tokenManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import retry from 'async-retry';
import dayjs from 'dayjs';
import { getConfig } from '../config';

let cachedToken: { value: string; expiresAt: number } | null = null;

export async function getAccessToken() {
if (cachedToken && cachedToken.expiresAt > Date.now() + 30_000) {
return cachedToken.value; // still fresh
}

const { clientId, clientSecret, authBaseUrl, fetchFn } = getConfig();
const doFetch = fetchFn ?? globalThis.fetch;

const auth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');

const body = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'content',
}).toString();

const res = await retry(
() =>
doFetch(`${authBaseUrl}/oauth2/token`, {
method: 'POST',
headers: {
Authorization: `Basic ${auth}`,
'content-type': 'application/x-www-form-urlencoded',
},
body,
}),
{ retries: 3 }
);

if (!res.ok) throw new Error(`Token request failed: ${res.statusText}`);

const json = (await res.json()) as {
access_token: string;
expires_in: number;
};

cachedToken = {
value: json.access_token,
expiresAt: dayjs().add(json.expires_in, 'second').valueOf(),
};
return cachedToken.value;
}
26 changes: 26 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface SdkConfig {
clientId: string;
clientSecret: string;
contentBaseUrl?: string; // for /content/api/v4
authBaseUrl?: string; // for /oauth2/token

fetchFn?: typeof fetch;
}

let sdkConfig: SdkConfig;

export function configure(config: SdkConfig) {
sdkConfig = {
contentBaseUrl: 'https://apis.quran.foundation', // ✅ for all content endpoints
authBaseUrl: 'https://oauth2.quran.foundation', // ✅ for token endpoint

...config,
};
}

export function getConfig(): SdkConfig {
if (!sdkConfig) {
throw new Error('SDK not configured – call configure() first');
}
return sdkConfig;
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { configure } from './config';
export { default as quran } from './sdk';
export * from './types';
Loading