Skip to content
Merged
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
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ Public Methods:
```ts
constructor(
secrets: ApiSecret[] // Api Secrets, see type below
concurrentOffset?: number, // default is 0, can be used to slow down the requests. ex: if your key can do 4 req/s you can set this to 1 to only make 3 req/s. Usefull if your backend or db can't keep up.
jobExpiration?: number, // default is 20000ms, especially important when using redis to kill infinite jobs
redisConfig?: RedisConfig // config to connect to redis, see below
settings?: Fast42Settings
);

interface Fast42Settings {
concurrentOffset?: number; // default is 0, can be used to slow down the requests. ex: if your key can do 4 req/s you can set this to 1 to only make 3 req/s. Usefull if your backend or db can't keep up.
jobExpiration?: number; // default is 60000ms, especially important when using redis to kill infinite jobs
redisConfig?: RedisConfig; // config to connect to redis, see below
scopes?: string[]; // default is ['public', 'projects']
}

interface ApiSecret {
client_id: string;
client_secret: string;
Expand Down Expand Up @@ -160,7 +165,7 @@ async function main() {
client_id: process.env['FTAPI_UID1'],
client_secret: process.env['FTAPI_SECRET1'],
}
], 1).init()
], { concurrentOffset: 1 }).init()
await getAll42Cursus(api);
}
```
Expand All @@ -172,13 +177,15 @@ Usage with redis:
client_id: process.env['FTAPI_UID'],
client_secret: process.env['FTAPI_SECRET'],
},
],
0,
20000, // setting an expiration on all jobs is important when clustering!
{
], {
concurrentOffset: 0,
jobExpiration: 20000, // setting an expiration on all jobs is important when clustering!
redisConfig: {
host: "127.0.0.1",
port: 6379,
password: "somepassword"
},
scopes: ["public", "projects"],
}).init());
const job = await api.get("/projects/1");
const item = await job.json();
Expand Down
33 changes: 28 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ interface RedisConfig {
password?: string;
}

interface Fast42Settings {
concurrentOffset?: number;
jobExpiration?: number;
redisConfig?: RedisConfig;
scopes?: string[];
}

enum Method {
GET = 'GET',
POST = 'POST',
Expand All @@ -60,6 +67,7 @@ class Fast42 {
private NOTINITIALIZED = "Fast42 is not initialized. Call init() first"
private _redisConfig: RedisConfig | undefined;
private _jobExpiration: number;
private _scopes: string[];

/**
* Constructs the api42 class
Expand All @@ -73,19 +81,34 @@ class Fast42 {
* This is useful if you want to run multiple instances of your application, and want to share the rate limit counters between them.
*
*/
constructor(secrets: ApiSecret[], concurrentOffset: number = 0, jobExpiration: number = 60000, redisConfig?: RedisConfig) {
constructor(secrets: ApiSecret[], settings?: Fast42Settings)
constructor(secrets: ApiSecret[], concurrentOffset?: number, jobExpiration?: number, redisConfig?: RedisConfig)
constructor(
secrets: ApiSecret[],
settingsOrConcurrentOffset: Fast42Settings | number = 0,
jobExpiration: number = 60000,
redisConfig?: RedisConfig,
) {
if (secrets.length === 0) {
throw new Error("Fast42 requires at least one 42 Api Key/Secret pair")
}
const settings: Fast42Settings = typeof settingsOrConcurrentOffset === 'object'
? settingsOrConcurrentOffset
: {
concurrentOffset: settingsOrConcurrentOffset,
jobExpiration,
redisConfig,
}
this._secrets = secrets
this._rootUrl = "https://api.intra.42.fr/v2"
this._cache = new NodeCache()
this._limiterPairs = []
this._keyCount = secrets.length
this._currentIndex = 0
this._concurrentOffset = concurrentOffset
this._redisConfig = redisConfig
this._jobExpiration = jobExpiration
this._concurrentOffset = settings.concurrentOffset ?? 0
this._redisConfig = settings.redisConfig
this._jobExpiration = settings.jobExpiration ?? 60000
this._scopes = settings.scopes ?? ['public', 'projects']
}

/*
Expand Down Expand Up @@ -317,7 +340,7 @@ class Fast42 {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: `grant_type=client_credentials&client_id=${clientid}&client_secret=${clientsecret}&scope=projects%20public`
body: `grant_type=client_credentials&client_id=${clientid}&client_secret=${clientsecret}&scope=${this._scopes.join('%20')}`
})
if (!response.ok) {
throw new Error(`Error getting access token: ${response.status} ${response.statusText}`)
Expand Down
60 changes: 60 additions & 0 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import Fast42 from '../src/index';
import fetch from 'node-fetch';

jest.mock('node-fetch', () => jest.fn());

const client_id = "test";
const client_secret = "test";
const mockedFetch = fetch as unknown as jest.Mock;

beforeEach(() => {
mockedFetch.mockReset();
});

it("Should instantiate", () => {
const api = new Fast42([
Expand All @@ -27,6 +35,58 @@ it("Should instantiate using Redis", () => {
expect(api).toBeInstanceOf(Fast42);
})

it("Should instantiate with settings object and scopes", () => {
const api = new Fast42([
{
client_id: client_id,
client_secret: client_secret,
},
], {
concurrentOffset: 0,
jobExpiration: 2000,
scopes: ['public', 'projects', 'profile'],
});
expect(api).toBeInstanceOf(Fast42);
})

it("Should default scopes to public and projects", () => {
const api = new Fast42([
{
client_id: client_id,
client_secret: client_secret,
},
]);
expect((api as any)._scopes).toEqual(['public', 'projects']);
})

it("Should use configured scopes when requesting access token", async () => {
mockedFetch.mockResolvedValue({
ok: true,
json: async () => ({
access_token: 'token',
token_type: 'bearer',
expires_in: 7200,
scope: 'public projects profile',
created_at: 0
})
} as any);
const api = new Fast42([
{
client_id: client_id,
client_secret: client_secret,
},
], {
scopes: ['public', 'projects', 'profile'],
});
await (api as any).getAccessToken('id', 'secret');
expect(mockedFetch).toHaveBeenCalledWith(
"https://api.intra.42.fr/oauth/token",
expect.objectContaining({
body: expect.stringContaining("scope=public%20projects%20profile")
})
);
})

// it("initializes using real keys", async () => {
// const api = await (new Fast42([
// {
Expand Down
Loading