@omerlo/omerlo-webkit 0.0.4 → 0.0.5

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.
Files changed (36) hide show
  1. package/README.md +24 -22
  2. package/dist/components/OmerloWebkit.svelte +2 -3
  3. package/dist/components/OmerloWebkit.svelte.d.ts +1 -1
  4. package/dist/omerlo/index.d.ts +4 -2
  5. package/dist/omerlo/reader/endpoints/categories.d.ts +18 -0
  6. package/dist/omerlo/reader/endpoints/categories.js +36 -0
  7. package/dist/omerlo/reader/endpoints/content-templates.d.ts +16 -0
  8. package/dist/omerlo/reader/endpoints/content-templates.js +1 -0
  9. package/dist/omerlo/reader/endpoints/contents.d.ts +51 -0
  10. package/dist/omerlo/reader/endpoints/contents.js +2 -0
  11. package/dist/omerlo/reader/endpoints/device.js +1 -1
  12. package/dist/omerlo/reader/endpoints/notification.d.ts +5 -5
  13. package/dist/omerlo/reader/endpoints/notification.js +5 -7
  14. package/dist/omerlo/reader/endpoints/oauth.js +1 -1
  15. package/dist/omerlo/reader/endpoints/visuals.d.ts +16 -0
  16. package/dist/omerlo/reader/endpoints/visuals.js +1 -0
  17. package/dist/omerlo/reader/fetchers.d.ts +4 -2
  18. package/dist/omerlo/reader/fetchers.js +3 -1
  19. package/dist/omerlo/reader/server/email.d.ts +12 -0
  20. package/dist/omerlo/reader/server/email.js +30 -0
  21. package/dist/omerlo/reader/server/hooks.d.ts +1 -1
  22. package/dist/omerlo/reader/server/hooks.js +25 -20
  23. package/dist/omerlo/reader/server/index.d.ts +1 -0
  24. package/dist/omerlo/reader/server/index.js +1 -0
  25. package/dist/omerlo/reader/server/token.d.ts +6 -6
  26. package/dist/omerlo/reader/server/token.js +7 -7
  27. package/dist/omerlo/reader/server/utils.d.ts +4 -4
  28. package/dist/omerlo/reader/server/utils.js +13 -7
  29. package/dist/omerlo/reader/stores/user_session.d.ts +2 -2
  30. package/dist/omerlo/reader/stores/user_session.js +11 -9
  31. package/dist/omerlo/reader/utils/request.d.ts +10 -8
  32. package/dist/omerlo/reader/utils/request.js +39 -11
  33. package/dist/omerlo/reader/utils/response.d.ts +1 -1
  34. package/dist/omerlo/reader/utils/response.js +1 -1
  35. package/eslint.config.js +1 -1
  36. package/package.json +2 -2
package/README.md CHANGED
@@ -24,17 +24,17 @@ Copy the `.env.dist` to `.env` then update its values to match your Omerlo's app
24
24
  Create (or update) the file `src/hooks.server.ts` and add required hooks as follow
25
25
 
26
26
  ```ts
27
- import type { Handle } from "@sveltejs/kit";
27
+ import type { Handle } from '@sveltejs/kit';
28
28
 
29
29
  import { handleUserToken, handleReaderApi } from 'omerlo-webkit/reader/server';
30
- import { sequence } from "@sveltejs/kit/hooks";
30
+ import { sequence } from '@sveltejs/kit/hooks';
31
31
 
32
32
  // You can add custom hooks too
33
33
  const handleLocale: Handle = async ({ event, resolve }) => {
34
- const userLocale = 'en'; // you can fetch this value from cookie or w.e
35
- event.url.searchParams.append('locale', userLocale);
36
- return resolve(event);
37
- }
34
+ const userLocale = 'en'; // you can fetch this value from cookie or w.e
35
+ event.url.searchParams.append('locale', userLocale);
36
+ return resolve(event);
37
+ };
38
38
 
39
39
  export const handle = sequence(handleLocale, handleUserToken, handleReaderApi);
40
40
  ```
@@ -92,7 +92,7 @@ You'll need to create a login endpoint to add some required params such as the `
92
92
 
93
93
  ```ts
94
94
  // +server.ts
95
- import { error, redirect, type RequestHandler } from "@sveltejs/kit";
95
+ import { error, redirect, type RequestHandler } from '@sveltejs/kit';
96
96
  import { env } from '$env/dynamic/private';
97
97
  import jwt from 'jsonwebtoken';
98
98
 
@@ -104,39 +104,41 @@ export const GET: RequestHandler = ({ url }) => {
104
104
  if (!oauthProviderId) error(400, 'Missing oauthProviderId query parameter');
105
105
 
106
106
  const currentPath = url.searchParams.get('currentPath') || '/';
107
- const state = jwt.sign({ currentPath, oauthProviderId }, env.PRIVATE_JWT_SECRET, { expiresIn: '1h' })
107
+ const state = jwt.sign({ currentPath, oauthProviderId }, env.PRIVATE_JWT_SECRET, {
108
+ expiresIn: '1h'
109
+ });
108
110
 
109
111
  const redirectUrl = new URL(oauthUrl);
110
112
  redirectUrl.searchParams.set('state', state);
111
- redirectUrl.searchParams.set('redirect_uri', url.origin +'/oauth/callback');
113
+ redirectUrl.searchParams.set('redirect_uri', url.origin + '/oauth/callback');
112
114
 
113
115
  redirect(302, redirectUrl);
114
- }
116
+ };
115
117
  ```
116
118
 
117
119
  And then create a callback action.
118
120
 
119
121
  ```ts
120
- import { error, redirect, type RequestHandler } from "@sveltejs/kit";
122
+ import { error, redirect, type RequestHandler } from '@sveltejs/kit';
121
123
  import { env } from '$env/dynamic/private';
122
124
  import jwt from 'jsonwebtoken';
123
- import { exchangeAuthorizationCode, setAuthorizationCookies } from "omerlo-webkit/reader/server";
125
+ import { exchangeAuthorizationCode, setAuthorizationCookies } from 'omerlo-webkit/reader/server';
124
126
 
125
127
  export const GET: RequestHandler = async ({ url, cookies }) => {
126
128
  const redirectUri = url.origin + url.pathname;
127
129
  const state = getRequiredQueryParams(url, 'state');
128
130
  const code = getRequiredQueryParams(url, 'code');
129
- const {oauthProviderId, currentPath} = parseJwt(state);
131
+ const { oauthProviderId, currentPath } = parseJwt(state);
130
132
 
131
133
  try {
132
- const token = await exchangeAuthorizationCode({code, redirectUri, oauthProviderId})
134
+ const token = await exchangeAuthorizationCode({ code, redirectUri, oauthProviderId });
133
135
  setAuthorizationCookies(cookies, token);
134
- } catch(_err) {
135
- error(401, "Could not authenticate from the provider");
136
+ } catch (_err) {
137
+ error(401, 'Could not authenticate from the provider');
136
138
  }
137
139
 
138
140
  redirect(303, currentPath);
139
- }
141
+ };
140
142
 
141
143
  function getRequiredQueryParams(url: URL, paramsName: string): string {
142
144
  const value = url.searchParams.get(paramsName);
@@ -149,14 +151,14 @@ function getRequiredQueryParams(url: URL, paramsName: string): string {
149
151
  }
150
152
 
151
153
  interface State {
152
- oauthProviderId: string,
153
- currentPath: string
154
+ oauthProviderId: string;
155
+ currentPath: string;
154
156
  }
155
157
 
156
158
  function parseJwt(state: string): State {
157
159
  try {
158
160
  return jwt.verify(state, env.PRIVATE_JWT_SECRET) as State;
159
- } catch(_err) {
161
+ } catch (_err) {
160
162
  error(400, 'Invalid state');
161
163
  }
162
164
  }
@@ -168,13 +170,13 @@ To logout your user you can create an endpoint that will drop cookies used for a
168
170
 
169
171
  ```ts
170
172
  // +server.ts
171
- import { json, type RequestHandler } from "@sveltejs/kit";
173
+ import { json, type RequestHandler } from '@sveltejs/kit';
172
174
  import { clearAuthorizationCookies } from 'omerlo-webkit/reader/server';
173
175
 
174
176
  export const DELETE: RequestHandler = ({ cookies }) => {
175
177
  clearAuthorizationCookies(cookies);
176
178
  return json(201);
177
- }
179
+ };
178
180
  ```
179
181
 
180
182
  When a user is logout for any reason (401 or manually logout), the `invalidate('omerlo:user_session)` is triggered,
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import { initUserSession, type UserSession } from "../omerlo/reader/stores/user_session";
3
- import { onDestroy, onMount } from "svelte";
2
+ import { initUserSession, type UserSession } from '../omerlo/reader/stores/user_session';
3
+ import { onDestroy, onMount } from 'svelte';
4
4
 
5
5
  export let userSession: UserSession;
6
6
  let selfComponent: HTMLDivElement;
@@ -23,4 +23,3 @@
23
23
  <div id="omerlo-webkit" bind:this={selfComponent}>
24
24
  <slot></slot>
25
25
  </div>
26
-
@@ -1,4 +1,4 @@
1
- import { type UserSession } from "../omerlo/reader/stores/user_session";
1
+ import { type UserSession } from '../omerlo/reader/stores/user_session';
2
2
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
3
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
4
  $$bindings?: Bindings;
@@ -1,9 +1,11 @@
1
1
  export declare const useReader: (f: typeof fetch) => {
2
2
  notifications: {
3
3
  listTopics: (params?: Partial<import("./reader/utils/api").PagingParams>) => Promise<import("./reader/utils/api").ApiResponse<import("./reader/endpoints/notification").TopicSummary[]>>;
4
- subscribeToTopic: (params: import("./reader/endpoints/notification").SubscribtionParams) => Promise<import("./reader/utils/api").ApiResponse<unknown>>;
5
- unsubscribeFromTopic: (params: import("./reader/endpoints/notification").SubscribtionParams) => Promise<import("./reader/utils/api").ApiResponse<unknown>>;
4
+ subscribeToTopic: (params: import("./reader/endpoints/notification").SubscriptionParams) => Promise<import("./reader/utils/api").ApiResponse<unknown>>;
5
+ unsubscribeFromTopic: (params: import("./reader/endpoints/notification").SubscriptionParams) => Promise<import("./reader/utils/api").ApiResponse<unknown>>;
6
6
  };
7
+ listCategories: (params?: Partial<import("./reader/utils/api").PagingParams>) => Promise<import("./reader/utils/api").ApiResponse<import("./reader/endpoints/categories").Category[]>>;
8
+ getCategory: (id: string) => Promise<import("./reader/utils/api").ApiResponse<import("./reader/endpoints/categories").Category>>;
7
9
  listOauthProviders: (params?: Partial<import("./reader/utils/api").PagingParams>) => Promise<import("./reader/utils/api").ApiResponse<import("./reader").OauthProviderSummary[]>>;
8
10
  getOauthUser: () => Promise<import("./reader/utils/api").ApiResponse<import("./reader").OauthUser>>;
9
11
  registerDevice: (params: import("./reader/endpoints/device").DeviceParams) => Promise<import("./reader/utils/api").ApiResponse<unknown>>;
@@ -0,0 +1,18 @@
1
+ import { type LocalesMetadata } from '../utils/response';
2
+ import { type ApiAssocs, type ApiData, type PagingParams } from '../utils/api';
3
+ export declare const categoriesFetchers: (f: typeof fetch) => {
4
+ listCategories: (params?: Partial<PagingParams>) => Promise<import("../utils/api").ApiResponse<Category[]>>;
5
+ getCategory: (id: string) => Promise<import("../utils/api").ApiResponse<Category>>;
6
+ };
7
+ export interface Category {
8
+ id: string;
9
+ svg: string;
10
+ name: string;
11
+ meta: {
12
+ locale: LocalesMetadata;
13
+ };
14
+ updatedAt: Date;
15
+ }
16
+ export declare function getCategory(f: typeof fetch): (id: string) => Promise<import("../utils/api").ApiResponse<Category>>;
17
+ export declare function listCategories(f: typeof fetch): (params?: Partial<PagingParams>) => Promise<import("../utils/api").ApiResponse<Category[]>>;
18
+ export declare function parseCategory(data: ApiData, _assoc: ApiAssocs): Category;
@@ -0,0 +1,36 @@
1
+ import {} from '../utils/response';
2
+ import { parseMany } from '../utils/api';
3
+ import { requestPublisher } from '../utils/request';
4
+ export const categoriesFetchers = (f) => {
5
+ return {
6
+ listCategories: listCategories(f),
7
+ getCategory: getCategory(f)
8
+ };
9
+ };
10
+ export function getCategory(f) {
11
+ return async (id) => {
12
+ const opts = { parser: parseCategory };
13
+ return requestPublisher(f, `/categories/${id}`, opts);
14
+ };
15
+ }
16
+ export function listCategories(f) {
17
+ return async (params) => {
18
+ const queryParams = params;
19
+ const opts = { parser: parseMany(parseCategory), queryParams };
20
+ return requestPublisher(f, `/categories`, opts);
21
+ };
22
+ }
23
+ export function parseCategory(data, _assoc) {
24
+ return {
25
+ id: data.id,
26
+ svg: data.svg_icon,
27
+ name: data.localized.name,
28
+ updatedAt: data.updated_at,
29
+ meta: {
30
+ locale: {
31
+ available: [data.localized.locale],
32
+ current: data.localized.locale
33
+ }
34
+ }
35
+ };
36
+ }
@@ -0,0 +1,16 @@
1
+ import type { LocalesMetadata } from '../utils/response';
2
+ export interface ContentTemplate {
3
+ id: string;
4
+ key: string;
5
+ name: string;
6
+ meta: {
7
+ locales: LocalesMetadata;
8
+ };
9
+ metadata: Record<string, string>;
10
+ enableFields: string[];
11
+ updatedAt: Date;
12
+ }
13
+ export interface ContentBlockTemplate {
14
+ id: string;
15
+ key: string;
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import type { LocalesMetadata } from '../utils/response';
2
+ import type { Category } from './categories';
3
+ import type { ContentBlockTemplate, ContentTemplate } from './content-templates';
4
+ import type { Visual } from './visuals';
5
+ export interface ContentSummary {
6
+ id: string;
7
+ template: ContentTemplate;
8
+ metadata: Record<string, string>;
9
+ canonicalDomain: string;
10
+ canonicalUrl: string;
11
+ isArchived: boolean;
12
+ publishedAt?: Date;
13
+ visibility?: string;
14
+ categories: Category[];
15
+ show_published_at: boolean;
16
+ updatedAt: Date;
17
+ meta: {
18
+ locales: LocalesMetadata;
19
+ };
20
+ titleHtml: string;
21
+ titleText: string;
22
+ leadHtml: string;
23
+ leadText: string;
24
+ subtitleHtml: string;
25
+ subtitleText: string;
26
+ visual: Visual;
27
+ seo: ContentSeo;
28
+ }
29
+ export interface ContentSeo {
30
+ title: string;
31
+ description: string;
32
+ }
33
+ export interface Content extends ContentSummary {
34
+ blocks: ContentBlock[];
35
+ }
36
+ export type ContentBlock = ContentBlockRichtext | ContentBlockData;
37
+ export type ContentBlockRichtext = {
38
+ id: string;
39
+ template: ContentBlockTemplate;
40
+ visual?: Visual;
41
+ kind: 'richtext';
42
+ contentHtml: string;
43
+ };
44
+ export type ContentBlockData = {
45
+ id: string;
46
+ template: ContentBlockTemplate;
47
+ visual?: Visual;
48
+ kind: 'data';
49
+ contentType: string;
50
+ data: unknown;
51
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ // TODO others content's blocks
@@ -1,7 +1,7 @@
1
1
  import { request } from '../utils/request';
2
2
  export const deviceFetchers = (f) => {
3
3
  return {
4
- registerDevice: registerDevice(f),
4
+ registerDevice: registerDevice(f)
5
5
  };
6
6
  };
7
7
  export function registerDevice(f) {
@@ -2,8 +2,8 @@ import { type ApiAssocs, type ApiData, type PagingParams } from '../utils/api';
2
2
  import { type LocalesMetadata } from '../utils/response';
3
3
  export declare const notificationFetchers: (f: typeof fetch) => {
4
4
  listTopics: (params?: Partial<PagingParams>) => Promise<import("../utils/api").ApiResponse<TopicSummary[]>>;
5
- subscribeToTopic: (params: SubscribtionParams) => Promise<import("../utils/api").ApiResponse<unknown>>;
6
- unsubscribeFromTopic: (params: SubscribtionParams) => Promise<import("../utils/api").ApiResponse<unknown>>;
5
+ subscribeToTopic: (params: SubscriptionParams) => Promise<import("../utils/api").ApiResponse<unknown>>;
6
+ unsubscribeFromTopic: (params: SubscriptionParams) => Promise<import("../utils/api").ApiResponse<unknown>>;
7
7
  };
8
8
  export declare function listTopics(f: typeof fetch): (params?: Partial<PagingParams>) => Promise<import("../utils/api").ApiResponse<TopicSummary[]>>;
9
9
  export declare function parseTopicSummary(data: ApiData, _assocs: ApiAssocs): TopicSummary;
@@ -15,9 +15,9 @@ export interface TopicSummary {
15
15
  };
16
16
  updatedAt: Date;
17
17
  }
18
- export interface SubscribtionParams {
18
+ export interface SubscriptionParams {
19
19
  topicId: string;
20
20
  pushToken: string;
21
21
  }
22
- export declare function subscribeToTopic(f: typeof fetch): (params: SubscribtionParams) => Promise<import("../utils/api").ApiResponse<unknown>>;
23
- export declare function unsubscribeFromTopic(f: typeof fetch): (params: SubscribtionParams) => Promise<import("../utils/api").ApiResponse<unknown>>;
22
+ export declare function subscribeToTopic(f: typeof fetch): (params: SubscriptionParams) => Promise<import("../utils/api").ApiResponse<unknown>>;
23
+ export declare function unsubscribeFromTopic(f: typeof fetch): (params: SubscriptionParams) => Promise<import("../utils/api").ApiResponse<unknown>>;
@@ -5,7 +5,7 @@ export const notificationFetchers = (f) => {
5
5
  return {
6
6
  listTopics: listTopics(f),
7
7
  subscribeToTopic: subscribeToTopic(f),
8
- unsubscribeFromTopic: unsubscribeFromTopic(f),
8
+ unsubscribeFromTopic: unsubscribeFromTopic(f)
9
9
  };
10
10
  };
11
11
  export function listTopics(f) {
@@ -27,18 +27,16 @@ export function parseTopicSummary(data, _assocs) {
27
27
  export function subscribeToTopic(f) {
28
28
  return (params) => {
29
29
  const body = { push_token: params.pushToken };
30
- const headers = new Headers();
31
- headers.set('Content-Type', 'application/json');
32
- const opts = { method: 'post', body, headers };
30
+ const headers = new Headers({ 'Content-Type': 'application/json' });
31
+ const opts = { body, headers, method: 'post' };
33
32
  return request(f, `/topics/${params.topicId}/subscribe`, opts);
34
33
  };
35
34
  }
36
35
  export function unsubscribeFromTopic(f) {
37
36
  return (params) => {
38
37
  const body = { push_token: params.pushToken };
39
- const headers = new Headers();
40
- headers.set('Content-Type', 'application/json');
41
- const opts = { method: 'post', body, headers };
38
+ const headers = new Headers({ 'Content-Type': 'application/json' });
39
+ const opts = { body, headers, method: 'post' };
42
40
  return request(f, `/topics/${params.topicId}/unsubscribe`, opts);
43
41
  };
44
42
  }
@@ -3,7 +3,7 @@ import { parseMany } from '../utils/api';
3
3
  export const oauthFetchers = (f) => {
4
4
  return {
5
5
  listOauthProviders: listOauthProviders(f),
6
- getOauthUser: getOauthUser(f),
6
+ getOauthUser: getOauthUser(f)
7
7
  };
8
8
  };
9
9
  export function listOauthProviders(f) {
@@ -0,0 +1,16 @@
1
+ export type Visual = Image | Slideshow | Video;
2
+ export type Image = {
3
+ type: 'image';
4
+ url: string;
5
+ captionHtml: string;
6
+ captionText: string;
7
+ credit: string;
8
+ gravity: Gravity;
9
+ };
10
+ export type Slideshow = {
11
+ type: 'slideshow';
12
+ };
13
+ export type Video = {
14
+ type: 'video';
15
+ };
16
+ export type Gravity = 'center' | 'north' | 'northeast' | 'east' | 'southeast' | 'south' | 'southwest' | 'west' | 'northwest';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,9 +1,11 @@
1
1
  export declare const fetchers: (f: typeof fetch) => {
2
2
  notifications: {
3
3
  listTopics: (params?: Partial<import("./utils/api").PagingParams>) => Promise<import("./utils/api").ApiResponse<import("./endpoints/notification").TopicSummary[]>>;
4
- subscribeToTopic: (params: import("./endpoints/notification").SubscribtionParams) => Promise<import("./utils/api").ApiResponse<unknown>>;
5
- unsubscribeFromTopic: (params: import("./endpoints/notification").SubscribtionParams) => Promise<import("./utils/api").ApiResponse<unknown>>;
4
+ subscribeToTopic: (params: import("./endpoints/notification").SubscriptionParams) => Promise<import("./utils/api").ApiResponse<unknown>>;
5
+ unsubscribeFromTopic: (params: import("./endpoints/notification").SubscriptionParams) => Promise<import("./utils/api").ApiResponse<unknown>>;
6
6
  };
7
+ listCategories: (params?: Partial<import("./utils/api").PagingParams>) => Promise<import("./utils/api").ApiResponse<import("./endpoints/categories").Category[]>>;
8
+ getCategory: (id: string) => Promise<import("./utils/api").ApiResponse<import("./endpoints/categories").Category>>;
7
9
  listOauthProviders: (params?: Partial<import("./utils/api").PagingParams>) => Promise<import("./utils/api").ApiResponse<import("./endpoints/oauth").OauthProviderSummary[]>>;
8
10
  getOauthUser: () => Promise<import("./utils/api").ApiResponse<import("./endpoints/oauth").OauthUser>>;
9
11
  registerDevice: (params: import("./endpoints/device").DeviceParams) => Promise<import("./utils/api").ApiResponse<unknown>>;
@@ -1,4 +1,5 @@
1
1
  import { accountsFetchers } from './endpoints/accounts';
2
+ import { categoriesFetchers } from './endpoints/categories';
2
3
  import { deviceFetchers } from './endpoints/device';
3
4
  import { notificationFetchers } from './endpoints/notification';
4
5
  import { oauthFetchers } from './endpoints/oauth';
@@ -7,6 +8,7 @@ export const fetchers = (f) => {
7
8
  ...accountsFetchers(f),
8
9
  ...deviceFetchers(f),
9
10
  ...oauthFetchers(f),
10
- notifications: notificationFetchers(f),
11
+ ...categoriesFetchers(f),
12
+ notifications: notificationFetchers(f)
11
13
  };
12
14
  };
@@ -0,0 +1,12 @@
1
+ export interface EmailParams {
2
+ context: 'platform' | 'media';
3
+ to: string;
4
+ subject: string;
5
+ body: string;
6
+ }
7
+ /**
8
+ * Send an email.
9
+ *
10
+ * Return `true` if the email has been sent. It could take a couple of minutes to be received.
11
+ */
12
+ export declare function sendEmail(params: EmailParams): Promise<boolean>;
@@ -0,0 +1,30 @@
1
+ import { env } from '$env/dynamic/private';
2
+ import { ApiError } from '../utils/api';
3
+ import { getApplicationToken } from "./utils";
4
+ /**
5
+ * Send an email.
6
+ *
7
+ * Return `true` if the email has been sent. It could take a couple of minutes to be received.
8
+ */
9
+ export async function sendEmail(params) {
10
+ const url = getOmerloEndpoint();
11
+ const accessToken = await getApplicationToken();
12
+ const headers = new Headers({
13
+ 'x-omerlo-media-id': env.PRIVATE_OMERLO_MEDIA_ID,
14
+ Authorization: `Bearer ${accessToken}`,
15
+ 'Content-Type': 'text/html'
16
+ });
17
+ url.pathname = '/api/media/v1/mail/send';
18
+ url.searchParams.append('context', params.context);
19
+ url.searchParams.append('to', params.to);
20
+ url.searchParams.append('subject', params.subject);
21
+ const resp = await fetch(url, { method: 'POST', headers, body: params.body });
22
+ if (resp.ok) {
23
+ return Promise.resolve(true);
24
+ }
25
+ const payload = await resp.json();
26
+ throw new ApiError(resp.status, payload.error, resp.statusText);
27
+ }
28
+ function getOmerloEndpoint() {
29
+ return new URL(`${env.PRIVATE_OMERLO_PROTOCOL}://${env.PRIVATE_OMERLO_HOST}`);
30
+ }
@@ -1,3 +1,3 @@
1
- import { type Handle } from "@sveltejs/kit";
1
+ import { type Handle } from '@sveltejs/kit';
2
2
  export declare const proxyHook: Handle;
3
3
  export declare const handleUserToken: Handle;
@@ -1,27 +1,26 @@
1
- import { error } from "@sveltejs/kit";
1
+ import { error } from '@sveltejs/kit';
2
2
  import { env } from '$env/dynamic/private';
3
- import { ApiError } from "../utils/api";
4
- import { clearAuthorizationCookies, clearAuthorizationUsingHeader, getAccessTokenFromCookie, getApplicationToken, getRefreshTokenFromCookie, setAuthorizationCookies } from "./utils";
5
- import { refresh } from "./token";
3
+ import { ApiError } from '../utils/api';
4
+ import { clearAuthorizationCookies, clearAuthorizationUsingHeader, getAccessTokenFromCookie, getApplicationToken, getRefreshTokenFromCookie, setAuthorizationCookies } from './utils';
5
+ import { refresh } from './token';
6
6
  // NOTE: inspired by https://sami.website/blog/sveltekit-api-reverse-proxy
7
7
  const handleApiProxy = async ({ event, ...tail }) => {
8
+ // NOTE: It's important to reset the port BEFORE settings the host because the host could contains the port
9
+ // Example: localhost:4000
10
+ event.url.port = '';
8
11
  event.url.host = env.PRIVATE_OMERLO_HOST;
9
12
  event.url.protocol = env.PRIVATE_OMERLO_PROTOCOL;
10
- event.url.port = ''; // This to prevent custom posts when working with localhost
11
- let accessToken = event.locals.accessToken;
12
- if (!accessToken) {
13
- accessToken = await getApplicationToken();
14
- }
13
+ const accessToken = event.locals.accessToken ?? (await getApplicationToken());
15
14
  const body = event.request.body;
16
15
  const method = event.request.method;
17
- const headers = new Headers();
18
- headers.set('Content-Type', event.request.headers.get('content-type') ?? 'application/json');
19
- headers.set('x-omerlo-media-id', env.PRIVATE_OMERLO_MEDIA_ID ?? '');
20
- headers.set('Authorization', `Bearer ${accessToken}`);
21
- return await fetch(event.url.toString(), {
22
- body, headers, method,
23
- duplex: 'half'
24
- })
16
+ const headers = new Headers({
17
+ 'x-omerlo-media-id': env.PRIVATE_OMERLO_MEDIA_ID,
18
+ Authorization: `Bearer ${accessToken}`
19
+ });
20
+ const contentType = event.request.headers.get('content-type');
21
+ if (contentType)
22
+ headers.set('Content-Type', contentType);
23
+ return await fetch(event.url.toString(), { body, headers, method, duplex: 'half' })
25
24
  .then(async (resp) => {
26
25
  const headers = new Headers();
27
26
  if (resp.status === 401 && event.locals.accessToken) {
@@ -32,12 +31,12 @@ const handleApiProxy = async ({ event, ...tail }) => {
32
31
  const responseOpts = {
33
32
  headers: headers,
34
33
  status: resp.status,
35
- statusText: resp.statusText,
34
+ statusText: resp.statusText
36
35
  };
37
36
  return new Response(resp.body, responseOpts);
38
37
  })
39
38
  .catch((err) => {
40
- console.log("Could not proxy API request: ", err);
39
+ console.log('Could not proxy API request: ', err);
41
40
  error(500, 'Something went wrong');
42
41
  });
43
42
  };
@@ -45,13 +44,19 @@ export const proxyHook = async ({ event, resolve }) => {
45
44
  if (event.url.pathname.startsWith('/api/media/v1')) {
46
45
  return await handleApiProxy({ event, resolve });
47
46
  }
47
+ // TODO remove once every API will be done in Reader
48
+ if (event.url.pathname.startsWith('/api/publisher')) {
49
+ const mediaId = env.PRIVATE_OMERLO_MEDIA_ID;
50
+ event.url.pathname = event.url.pathname.replace('/api/publisher/', `/api/public/publisher/v2/medias/${mediaId}/`);
51
+ return handleApiProxy({ event, resolve });
52
+ }
48
53
  return resolve(event);
49
54
  };
50
55
  export const handleUserToken = async ({ event, resolve }) => {
51
56
  const accessToken = getAccessTokenFromCookie(event.cookies);
52
57
  const refreshToken = getRefreshTokenFromCookie(event.cookies);
53
58
  const opts = {
54
- filterSerializedResponseHeaders: (name) => name == 'x-logout',
59
+ filterSerializedResponseHeaders: (name) => name == 'x-logout'
55
60
  };
56
61
  if (accessToken) {
57
62
  event.locals.accessToken = accessToken;
@@ -2,3 +2,4 @@ export declare const handleReaderApi: import("@sveltejs/kit").Handle;
2
2
  export declare const handleUserToken: import("@sveltejs/kit").Handle;
3
3
  export * from './token';
4
4
  export * from './utils';
5
+ export * from './email';
@@ -3,3 +3,4 @@ export const handleReaderApi = Hooks.proxyHook;
3
3
  export const handleUserToken = Hooks.handleUserToken;
4
4
  export * from './token';
5
5
  export * from './utils';
6
+ export * from './email';
@@ -4,16 +4,16 @@ export interface exchangeAuthorizationCodeParams {
4
4
  oauthProviderId: string;
5
5
  }
6
6
  /**
7
- * Exchange an authorization code for an omerlo's token.
8
- */
7
+ * Exchange an authorization code for an omerlo's token.
8
+ */
9
9
  export declare function exchangeAuthorizationCode(params: exchangeAuthorizationCodeParams): Promise<OmerloToken>;
10
10
  /**
11
- * Refresh the user's token.
12
- */
11
+ * Refresh the user's token.
12
+ */
13
13
  export declare function refresh(refreshToken: string): Promise<OmerloToken>;
14
14
  /**
15
- * Authenticate anonymously a user to get an anonymous token.
16
- */
15
+ * Authenticate anonymously a user to get an anonymous token.
16
+ */
17
17
  export declare function getAnonymousToken(scope: string): Promise<OmerloToken>;
18
18
  export interface OmerloToken {
19
19
  accessToken: string;
@@ -1,8 +1,8 @@
1
1
  import { env } from '$env/dynamic/private';
2
2
  import { ApiError } from '../utils/api';
3
3
  /**
4
- * Exchange an authorization code for an omerlo's token.
5
- */
4
+ * Exchange an authorization code for an omerlo's token.
5
+ */
6
6
  export async function exchangeAuthorizationCode(params) {
7
7
  const url = getTokenEndpoint();
8
8
  url.pathname = '/api/media/v1/oauth/token';
@@ -19,8 +19,8 @@ export async function exchangeAuthorizationCode(params) {
19
19
  throw new ApiError(resp.status, payload.error, resp.statusText);
20
20
  }
21
21
  /**
22
- * Refresh the user's token.
23
- */
22
+ * Refresh the user's token.
23
+ */
24
24
  export async function refresh(refreshToken) {
25
25
  const url = getTokenEndpoint();
26
26
  url.pathname = '/api/media/v1/oauth/token';
@@ -34,8 +34,8 @@ export async function refresh(refreshToken) {
34
34
  throw new ApiError(resp.status, payload.error, resp.statusText);
35
35
  }
36
36
  /**
37
- * Authenticate anonymously a user to get an anonymous token.
38
- */
37
+ * Authenticate anonymously a user to get an anonymous token.
38
+ */
39
39
  export async function getAnonymousToken(scope) {
40
40
  const url = getTokenEndpoint();
41
41
  url.pathname = '/api/media/v1/oauth/token';
@@ -58,6 +58,6 @@ function parseTokenResponse(data) {
58
58
  accessToken: data.access_token,
59
59
  refreshToken: data.refresh_token,
60
60
  expiresIn: data.expires_in,
61
- tokenType: data.token_type,
61
+ tokenType: data.token_type
62
62
  };
63
63
  }
@@ -1,6 +1,6 @@
1
- import type { UserSession } from "../stores/user_session";
1
+ import type { UserSession } from '../stores/user_session';
2
2
  import type { Cookies } from '@sveltejs/kit';
3
- import { type OmerloToken } from "./token";
3
+ import { type OmerloToken } from './token';
4
4
  export declare function loadUserSession(f: typeof fetch, cookies: Cookies): Promise<UserSession>;
5
5
  export declare function isAuthenticated(cookies: Cookies): boolean;
6
6
  export declare function setAuthorizationCookies(cookies: Cookies, token: OmerloToken): void;
@@ -9,6 +9,6 @@ export declare function clearAuthorizationUsingHeader(headers: Headers): void;
9
9
  export declare function getAccessTokenFromCookie(cookies: Cookies): string | null;
10
10
  export declare function getRefreshTokenFromCookie(cookies: Cookies): string | null;
11
11
  /**
12
- * Get the token used by the application.
13
- */
12
+ * Get the token used by the application.
13
+ */
14
14
  export declare function getApplicationToken(): Promise<string>;
@@ -1,11 +1,13 @@
1
- import { useReader } from "../..";
2
- import { getAnonymousToken, refresh } from "./token";
1
+ import { useReader } from '../..';
2
+ import { getAnonymousToken, refresh } from './token';
3
3
  export async function loadUserSession(f, cookies) {
4
4
  const userSession = { verified: false, authenticated: false, user: null };
5
5
  if (isAuthenticated(cookies)) {
6
6
  userSession.authenticated = true;
7
7
  try {
8
- const userInfo = await useReader(f).userInfo().then((resp) => resp.data);
8
+ const userInfo = await useReader(f)
9
+ .userInfo()
10
+ .then((resp) => resp.data);
9
11
  userSession.verified = true;
10
12
  userSession.user = userInfo;
11
13
  }
@@ -22,7 +24,11 @@ const accessTokenCookieName = 'access_token';
22
24
  const refreshTokenCookieName = 'refresh_token';
23
25
  export function setAuthorizationCookies(cookies, token) {
24
26
  cookies.set('logged_in', 'true', { path: '/', httpOnly: false });
25
- cookies.set(accessTokenCookieName, token.accessToken, { httpOnly: true, path: '/', maxAge: token.expiresIn - 60 });
27
+ cookies.set(accessTokenCookieName, token.accessToken, {
28
+ httpOnly: true,
29
+ path: '/',
30
+ maxAge: token.expiresIn - 60
31
+ });
26
32
  cookies.set(refreshTokenCookieName, token.refreshToken, { httpOnly: true, path: '/' });
27
33
  }
28
34
  export function clearAuthorizationCookies(cookies) {
@@ -46,11 +52,11 @@ const applicationToken = {
46
52
  accessToken: '',
47
53
  refreshToken: '',
48
54
  expiredAt: 0,
49
- init: false,
55
+ init: false
50
56
  };
51
57
  /**
52
- * Get the token used by the application.
53
- */
58
+ * Get the token used by the application.
59
+ */
54
60
  export async function getApplicationToken() {
55
61
  if (!applicationToken.init) {
56
62
  await newApplicationToken();
@@ -1,5 +1,5 @@
1
- import type { UserInfo } from "../endpoints/accounts";
2
- import { type Readable } from "svelte/store";
1
+ import type { UserInfo } from '../endpoints/accounts';
2
+ import { type Readable } from 'svelte/store';
3
3
  export interface UserSession {
4
4
  user: UserInfo | null;
5
5
  verified: boolean;
@@ -1,12 +1,12 @@
1
- import { getContext, setContext } from "svelte";
2
- import { writable } from "svelte/store";
3
- import { browser } from "$app/environment";
4
- import { useReader } from "../..";
5
- import { invalidate } from "$app/navigation";
1
+ import { getContext, setContext } from 'svelte';
2
+ import { writable } from 'svelte/store';
3
+ import { browser } from '$app/environment';
4
+ import { useReader } from '../..';
5
+ import { invalidate } from '$app/navigation';
6
6
  const anonymousUserSession = {
7
7
  user: null,
8
8
  verified: false,
9
- authenticated: false,
9
+ authenticated: false
10
10
  };
11
11
  export function initUserSession(session) {
12
12
  const { subscribe, update, set } = writable(session);
@@ -28,16 +28,18 @@ export function initUserSession(session) {
28
28
  subscribe,
29
29
  handleLogout: () => {
30
30
  if (!browser) {
31
- throw new Error("MUST NOT call refresh on user session from server side.");
31
+ throw new Error('MUST NOT call refresh on user session from server side.');
32
32
  }
33
33
  invalidate('omerlo:user_session');
34
34
  update(updateUserInfo(null, false));
35
35
  },
36
36
  refresh: async () => {
37
37
  if (!browser) {
38
- throw new Error("MUST NOT call refresh on user session from server side.");
38
+ throw new Error('MUST NOT call refresh on user session from server side.');
39
39
  }
40
- const userInfo = await useReader(fetch).userInfo().then((resp) => resp.data);
40
+ const userInfo = await useReader(fetch)
41
+ .userInfo()
42
+ .then((resp) => resp.data);
41
43
  update(updateUserInfo(userInfo, true));
42
44
  }
43
45
  };
@@ -1,17 +1,19 @@
1
1
  import type { ApiAssocs, ApiData, ApiResponse } from './api';
2
- type FetchOptions<T> = {
3
- parser?: (data: ApiData, assocs: ApiAssocs) => T;
4
- queryParams?: ApiData;
5
- method?: 'get' | 'post' | 'put' | 'delete';
6
- body?: ApiData;
7
- headers?: Headers;
2
+ type FetchOptions<T> = Required<DirtyFetchOptions> & {
3
+ parser: (data: ApiData, assocs: ApiAssocs) => T;
8
4
  };
9
- export declare function request<T>(f: typeof fetch, path: string, opts: FetchOptions<T>): Promise<ApiResponse<T>>;
5
+ export declare function request<T>(f: typeof fetch, path: string, opts: Partial<FetchOptions<T>>): Promise<ApiResponse<T>>;
10
6
  type DirtyFetchOptions = {
11
7
  queryParams?: ApiData;
12
- method?: 'get' | 'post' | 'put' | 'delete';
8
+ method?: 'get' | 'post' | 'patch' | 'put' | 'delete';
13
9
  body?: ApiData;
14
10
  headers?: Headers;
15
11
  };
16
12
  export declare function dirtyRequest(f: typeof fetch, path: string, opts: DirtyFetchOptions): Promise<Response>;
13
+ /**
14
+ * NOTE: This is for OLD api and will be removed once every API will
15
+ * be developped in Reader.
16
+ */
17
+ export declare function requestPublisher<T>(f: typeof fetch, path: string, opts: Partial<FetchOptions<T>>): Promise<ApiResponse<T>>;
18
+ export declare function dirtyRequestPublisher(f: typeof fetch, path: string, opts: DirtyFetchOptions): Promise<Response>;
17
19
  export {};
@@ -1,17 +1,24 @@
1
1
  import { parseApiResponse } from './api';
2
2
  import { BROWSER } from 'esm-env';
3
- export async function request(f, path, opts) {
4
- const parser = opts.parser || ((data) => data);
5
- const body = JSON.stringify(opts.body);
6
- const headers = opts.headers;
7
- const method = opts.method ?? 'get';
8
- const queryParams = opts.queryParams;
9
- // Enforce JSON content type for posts
10
- if (!opts.headers) {
11
- opts.headers = new Headers();
3
+ function parseRequestOpts(params) {
4
+ const headers = params.headers ?? new Headers();
5
+ const method = params.method ?? 'get';
6
+ if (!headers.has('Accept')) {
7
+ headers.set('Accept', 'application/json');
8
+ }
9
+ if (!headers.has('Content-Type') && ['post', 'put', 'patch'].includes(method)) {
10
+ headers.set('Content-Type', 'application/json');
12
11
  }
13
- if (['post', 'put'].includes(method) && !opts.headers.get('Content-Type'))
14
- opts.headers.set('Content-Type', 'application/json');
12
+ return {
13
+ queryParams: params.queryParams,
14
+ body: JSON.stringify(params.body),
15
+ method: params.method ?? 'get',
16
+ headers: headers,
17
+ parser: params.parser || ((data) => data)
18
+ };
19
+ }
20
+ export async function request(f, path, opts) {
21
+ const { body, headers, method, queryParams, parser } = parseRequestOpts(opts);
15
22
  return dirtyRequest(f, path, { body, headers, method, queryParams }).then(async (resp) => {
16
23
  return parseApiResponse(resp, parser);
17
24
  });
@@ -34,3 +41,24 @@ export async function dirtyRequest(f, path, opts) {
34
41
  }
35
42
  return resp;
36
43
  }
44
+ /**
45
+ * NOTE: This is for OLD api and will be removed once every API will
46
+ * be developped in Reader.
47
+ */
48
+ export async function requestPublisher(f, path, opts) {
49
+ const { body, headers, method, queryParams, parser } = parseRequestOpts(opts);
50
+ return dirtyRequestPublisher(f, path, { body, headers, method, queryParams }).then(async (resp) => {
51
+ return parseApiResponse(resp, parser);
52
+ });
53
+ }
54
+ export async function dirtyRequestPublisher(f, path, opts) {
55
+ const queryParams = new URLSearchParams();
56
+ path = `/api/publisher/${path}`;
57
+ if (opts?.queryParams) {
58
+ Object.entries(opts.queryParams).forEach(([key, value]) => {
59
+ queryParams.append(key, String(value));
60
+ });
61
+ path = `${path}?${queryParams}`;
62
+ }
63
+ return f(path.toString(), opts);
64
+ }
@@ -1,4 +1,4 @@
1
- import type { ApiData } from "./api";
1
+ import type { ApiData } from './api';
2
2
  export interface LocalesMetadata {
3
3
  current: string;
4
4
  available: string[];
@@ -1,6 +1,6 @@
1
1
  export function parseLocalesMetadata(meta) {
2
2
  return {
3
3
  current: meta.current,
4
- available: meta.available,
4
+ available: meta.available
5
5
  };
6
6
  }
package/eslint.config.js CHANGED
@@ -16,7 +16,7 @@ export default ts.config(
16
16
  languageOptions: {
17
17
  globals: {
18
18
  ...globals.browser,
19
- ...globals.node,
19
+ ...globals.node
20
20
  },
21
21
  parserOptions: {
22
22
  parser: ts.parser
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omerlo/omerlo-webkit",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run package",
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "description": "This webkit is a wapper arround Omerlo's API to create quickly a website using Omerlo.",
67
67
  "main": "eslint.config.js",
68
- "directories": { },
68
+ "directories": {},
69
69
  "repository": {
70
70
  "type": "git",
71
71
  "url": "git+https://github.com/Omerlo-Technologies/omerlo-webkit.git"