@secrecy/lib 1.86.0 → 1.86.1-feat-improve-sdk.2
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.
- package/dist/lib/base-client.js +6 -5
- package/dist/lib/cache.js +14 -5
- package/dist/lib/client/SecrecyMailClient.js +12 -5
- package/dist/lib/client/helpers.js +34 -2
- package/dist/lib/client/index.js +6 -8
- package/dist/lib/client.js +6 -10
- package/dist/lib/crypto/index.js +9 -2
- package/dist/lib/error/client.js +7 -1
- package/dist/lib/react/index.js +69 -0
- package/dist/lib/utils/popup-tools.js +20 -12
- package/dist/types/base-client.d.ts +2 -0
- package/dist/types/cache.d.ts +7 -6
- package/dist/types/client/SecrecyMailClient.d.ts +23 -1
- package/dist/types/client/helpers.d.ts +13 -0
- package/dist/types/client/index.d.ts +4 -2
- package/dist/types/client.d.ts +1 -0
- package/dist/types/error/client.d.ts +8 -0
- package/dist/types/error/index.d.ts +4 -0
- package/dist/types/react/index.d.ts +20 -0
- package/dist/types/utils/popup-tools.d.ts +3 -5
- package/package.json +22 -7
- package/dist/lib/client/SecrecyDbClient.js +0 -3
- package/dist/types/client/SecrecyDbClient.d.ts +0 -4
package/dist/lib/base-client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { usersCache } from './cache.js';
|
|
1
|
+
import { usersCache, clearAllCaches } from './cache.js';
|
|
2
2
|
import { getStorage } from './client/storage.js';
|
|
3
3
|
import { createTRPCClient, } from './client.js';
|
|
4
4
|
import { getPreferedEmail } from './utils.js';
|
|
@@ -46,15 +46,16 @@ export class BaseClient {
|
|
|
46
46
|
BaseClient.getBaseClient({
|
|
47
47
|
session: opts.session,
|
|
48
48
|
apiUrl: this.secrecyUrls.api,
|
|
49
|
+
timeoutMs: opts.timeoutMs,
|
|
49
50
|
onAccessDenied: async () => {
|
|
50
|
-
console.log('[BASE_CLIENT - BEFORE] - Access denied');
|
|
51
51
|
await opts.onAccessDenied?.();
|
|
52
|
-
console.log('[BASE_CLIENT - AFTER] - Access denied');
|
|
53
52
|
try {
|
|
54
53
|
await this.logout();
|
|
55
54
|
}
|
|
56
55
|
finally {
|
|
57
|
-
|
|
56
|
+
if (opts.reloadOnAccessDenied ?? true) {
|
|
57
|
+
location.reload();
|
|
58
|
+
}
|
|
58
59
|
}
|
|
59
60
|
},
|
|
60
61
|
});
|
|
@@ -141,6 +142,6 @@ export class BaseClient {
|
|
|
141
142
|
session.identities.clear();
|
|
142
143
|
session.keyPairs.clear();
|
|
143
144
|
session.userAppSession.clear();
|
|
144
|
-
|
|
145
|
+
clearAllCaches();
|
|
145
146
|
};
|
|
146
147
|
}
|
package/dist/lib/cache.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { LRUCache } from 'lru-cache';
|
|
2
2
|
import { gigaToBytes } from './utils.js';
|
|
3
|
-
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
3
|
+
const boundedCacheOpts = { max: 5_000, ttl: 5 * 60 * 1000 };
|
|
4
|
+
export const dataCache = new LRUCache(boundedCacheOpts);
|
|
5
|
+
export const nodesCache = new LRUCache(boundedCacheOpts);
|
|
6
|
+
export const nodesEncryptionCache = new LRUCache(boundedCacheOpts);
|
|
6
7
|
export const getNodeForEncryptionFromCache = (id) => {
|
|
7
8
|
if (nodesEncryptionCache.has(id)) {
|
|
8
9
|
return nodesEncryptionCache.get(id);
|
|
@@ -12,8 +13,8 @@ export const getNodeForEncryptionFromCache = (id) => {
|
|
|
12
13
|
}
|
|
13
14
|
return undefined;
|
|
14
15
|
};
|
|
15
|
-
export const usersCache = new
|
|
16
|
-
export const publicKeysCache = new
|
|
16
|
+
export const usersCache = new LRUCache(boundedCacheOpts);
|
|
17
|
+
export const publicKeysCache = new LRUCache({ max: 10_000 });
|
|
17
18
|
export const dataContentCache = new LRUCache({
|
|
18
19
|
max: 500,
|
|
19
20
|
maxSize: gigaToBytes(0.5),
|
|
@@ -21,3 +22,11 @@ export const dataContentCache = new LRUCache({
|
|
|
21
22
|
return value.data.byteLength;
|
|
22
23
|
},
|
|
23
24
|
});
|
|
25
|
+
export function clearAllCaches() {
|
|
26
|
+
dataCache.clear();
|
|
27
|
+
nodesCache.clear();
|
|
28
|
+
nodesEncryptionCache.clear();
|
|
29
|
+
usersCache.clear();
|
|
30
|
+
publicKeysCache.clear();
|
|
31
|
+
dataContentCache.clear();
|
|
32
|
+
}
|
|
@@ -31,8 +31,13 @@ export class SecrecyMailClient {
|
|
|
31
31
|
})));
|
|
32
32
|
}
|
|
33
33
|
async create(data, customMessage) {
|
|
34
|
-
const
|
|
35
|
-
const
|
|
34
|
+
const { customMessage: embeddedCustomMessage, ...mailData } = data;
|
|
35
|
+
const resolvedCustomMessage = customMessage ?? embeddedCustomMessage;
|
|
36
|
+
const mail = await this.createDraft(mailData);
|
|
37
|
+
const isSent = await this.sendDraft({
|
|
38
|
+
draftId: mail.mailIntegrityId,
|
|
39
|
+
customMessage: resolvedCustomMessage,
|
|
40
|
+
});
|
|
36
41
|
if (!isSent) {
|
|
37
42
|
throw new Error('The mail does not sent!');
|
|
38
43
|
}
|
|
@@ -53,7 +58,8 @@ export class SecrecyMailClient {
|
|
|
53
58
|
}));
|
|
54
59
|
return waitingReceivedMailsWithIds;
|
|
55
60
|
}
|
|
56
|
-
async updateDraft(
|
|
61
|
+
async updateDraft(arg, args) {
|
|
62
|
+
const { draftId, body, subject, senderFiles, recipients, replyToId, } = typeof arg === 'string' ? { draftId: arg, ...args } : arg;
|
|
57
63
|
const drafts = await this.draftMails({});
|
|
58
64
|
const draft = drafts.find((d) => d.mailIntegrityId === draftId);
|
|
59
65
|
if (draft === undefined) {
|
|
@@ -124,7 +130,8 @@ export class SecrecyMailClient {
|
|
|
124
130
|
});
|
|
125
131
|
return isDeleted;
|
|
126
132
|
}
|
|
127
|
-
async sendDraft(
|
|
133
|
+
async sendDraft(arg, customMessage) {
|
|
134
|
+
const { draftId, customMessage: resolvedCustomMessage } = typeof arg === 'string' ? { draftId: arg, customMessage } : arg;
|
|
128
135
|
const drafts = await this.draftMails({});
|
|
129
136
|
const draft = drafts.find((d) => d.mailIntegrityId === draftId);
|
|
130
137
|
if (draft === undefined) {
|
|
@@ -157,7 +164,7 @@ export class SecrecyMailClient {
|
|
|
157
164
|
temporaryRecipients,
|
|
158
165
|
recipients,
|
|
159
166
|
id: draft.mailIntegrityId,
|
|
160
|
-
customMessage:
|
|
167
|
+
customMessage: resolvedCustomMessage ?? null,
|
|
161
168
|
});
|
|
162
169
|
return isSent;
|
|
163
170
|
}
|
|
@@ -2,14 +2,26 @@ import { SecrecyClient } from './index.js';
|
|
|
2
2
|
import { popup } from '../utils/popup-tools.js';
|
|
3
3
|
import { secrecyUserApp } from './types/index.js';
|
|
4
4
|
import { getStorage } from './storage.js';
|
|
5
|
+
import { setup } from '../sodium.js';
|
|
6
|
+
export function assertBrowser(fn) {
|
|
7
|
+
if (typeof window === 'undefined') {
|
|
8
|
+
const err = {
|
|
9
|
+
name: 'ClientError',
|
|
10
|
+
code: 'SERVER_ENVIRONMENT',
|
|
11
|
+
message: `${fn}() can only run in a browser. On Next.js, call it from a "use client" component, or via dynamic(..., { ssr: false }).`,
|
|
12
|
+
};
|
|
13
|
+
throw err;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
5
16
|
export function parseInfos() {
|
|
17
|
+
assertBrowser('parseInfos');
|
|
6
18
|
if (window.location.hash === '') {
|
|
7
19
|
return null;
|
|
8
20
|
}
|
|
9
|
-
const hash = window.location.hash.
|
|
21
|
+
const hash = window.location.hash.slice(1);
|
|
10
22
|
try {
|
|
11
23
|
const res = JSON.parse(atob(hash));
|
|
12
|
-
window.location.
|
|
24
|
+
history.replaceState(null, '', window.location.pathname + window.location.search);
|
|
13
25
|
const data = secrecyUserApp.safeParse(res);
|
|
14
26
|
return data.success ? data.data : null;
|
|
15
27
|
}
|
|
@@ -18,6 +30,7 @@ export function parseInfos() {
|
|
|
18
30
|
}
|
|
19
31
|
}
|
|
20
32
|
export function getSecrecyClient(opts = {}) {
|
|
33
|
+
assertBrowser('getSecrecyClient');
|
|
21
34
|
const storage = getStorage(opts.session);
|
|
22
35
|
const infos = parseInfos();
|
|
23
36
|
if (infos !== null) {
|
|
@@ -49,6 +62,7 @@ export function getSecrecyClient(opts = {}) {
|
|
|
49
62
|
return null;
|
|
50
63
|
}
|
|
51
64
|
export async function login({ appId, context, path, redirect, scopes, backPath, session, secrecyUrls, forceLogin, }) {
|
|
65
|
+
assertBrowser('login');
|
|
52
66
|
return await new Promise(async (resolve, reject) => {
|
|
53
67
|
const appUrl = window.location.origin;
|
|
54
68
|
const client = getSecrecyClient({ session, secrecyUrls });
|
|
@@ -129,3 +143,21 @@ export async function login({ appId, context, path, redirect, scopes, backPath,
|
|
|
129
143
|
}
|
|
130
144
|
});
|
|
131
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Single async entry point: runs `setup()`, reuses an existing session if
|
|
148
|
+
* one is found, and falls back to `login()` otherwise. Equivalent to
|
|
149
|
+
* `await setup()` + `getSecrecyClient()` + `login()`, without having to
|
|
150
|
+
* juggle the sync/async mismatch between them.
|
|
151
|
+
*/
|
|
152
|
+
export async function initSecrecy(opts) {
|
|
153
|
+
assertBrowser('initSecrecy');
|
|
154
|
+
await setup();
|
|
155
|
+
const existing = getSecrecyClient({
|
|
156
|
+
session: opts.session,
|
|
157
|
+
secrecyUrls: opts.secrecyUrls,
|
|
158
|
+
});
|
|
159
|
+
if (existing !== null) {
|
|
160
|
+
return existing;
|
|
161
|
+
}
|
|
162
|
+
return await login(opts);
|
|
163
|
+
}
|
package/dist/lib/client/index.js
CHANGED
|
@@ -2,8 +2,7 @@ import { BaseClient } from '../base-client.js';
|
|
|
2
2
|
import { SecrecyCloudClient } from './SecrecyCloudClient.js';
|
|
3
3
|
import { SecrecyMailClient } from './SecrecyMailClient.js';
|
|
4
4
|
import { SecrecyAppClient } from './SecrecyAppClient.js';
|
|
5
|
-
import {
|
|
6
|
-
import { SecrecyDbClient } from './SecrecyDbClient.js';
|
|
5
|
+
import { clearAllCaches } from '../cache.js';
|
|
7
6
|
import { SecrecyWalletClient } from './SecrecyWalletClient.js';
|
|
8
7
|
import { SecrecyPayClient } from './SecrecyPayClient.js';
|
|
9
8
|
import { SecrecyUserClient } from './SecrecyUserClient.js';
|
|
@@ -20,9 +19,10 @@ export class SecrecyClient extends BaseClient {
|
|
|
20
19
|
cloud;
|
|
21
20
|
mail;
|
|
22
21
|
app;
|
|
23
|
-
db;
|
|
24
22
|
organization;
|
|
23
|
+
/** @experimental Susceptible de changer sans version majeure. */
|
|
25
24
|
wallet;
|
|
25
|
+
/** @experimental Susceptible de changer sans version majeure. */
|
|
26
26
|
pay;
|
|
27
27
|
user;
|
|
28
28
|
pseudonym;
|
|
@@ -33,8 +33,9 @@ export class SecrecyClient extends BaseClient {
|
|
|
33
33
|
session: opts.uaSession,
|
|
34
34
|
secrecyUrls: opts.secrecyUrls,
|
|
35
35
|
apiClient: opts.apiClient,
|
|
36
|
+
timeoutMs: opts.timeoutMs,
|
|
37
|
+
reloadOnAccessDenied: opts.reloadOnAccessDenied,
|
|
36
38
|
onAccessDenied: async () => {
|
|
37
|
-
console.log('[CLIENT] - Access denied');
|
|
38
39
|
try {
|
|
39
40
|
await this.logout();
|
|
40
41
|
}
|
|
@@ -53,7 +54,6 @@ export class SecrecyClient extends BaseClient {
|
|
|
53
54
|
this.cloud = new SecrecyCloudClient(this);
|
|
54
55
|
this.mail = new SecrecyMailClient(this);
|
|
55
56
|
this.app = new SecrecyAppClient(opts.uaJwt, this);
|
|
56
|
-
this.db = new SecrecyDbClient(this);
|
|
57
57
|
this.organization = new SecrecyOrganizationClient(this);
|
|
58
58
|
this.wallet = new SecrecyWalletClient(this);
|
|
59
59
|
this.pay = new SecrecyPayClient(this);
|
|
@@ -104,9 +104,7 @@ export class SecrecyClient extends BaseClient {
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
async logout(sessionId) {
|
|
107
|
-
|
|
108
|
-
dataCache.clear();
|
|
109
|
-
publicKeysCache.clear();
|
|
107
|
+
clearAllCaches();
|
|
110
108
|
await super.logout(sessionId);
|
|
111
109
|
}
|
|
112
110
|
}
|
package/dist/lib/client.js
CHANGED
|
@@ -24,26 +24,22 @@ export const createTRPCClient = (opts) => innerCreateTRPCClient({
|
|
|
24
24
|
: 'https://api.secrecy.tech/trpc',
|
|
25
25
|
maxURLLength: 2083,
|
|
26
26
|
fetch: async (input, init) => {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const timeout = AbortSignal.timeout(opts.timeoutMs ?? 20_000);
|
|
28
|
+
const signal = init?.signal
|
|
29
|
+
? AbortSignal.any([init.signal, timeout])
|
|
30
|
+
: timeout;
|
|
31
31
|
const headers = new Headers(init?.headers);
|
|
32
32
|
headers.set('secrecy-lib-version', SECRECY_LIB_VERSION);
|
|
33
33
|
if (typeof opts.session === 'string') {
|
|
34
34
|
headers.set('secrecy-session', opts.session);
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
return await fetch(input, {
|
|
37
37
|
...init,
|
|
38
38
|
headers,
|
|
39
|
-
signal
|
|
39
|
+
signal,
|
|
40
40
|
mode: 'cors',
|
|
41
41
|
credentials: opts.session ? 'include' : 'omit',
|
|
42
42
|
});
|
|
43
|
-
void call.finally(() => {
|
|
44
|
-
clearTimeout(id);
|
|
45
|
-
});
|
|
46
|
-
return await call;
|
|
47
43
|
},
|
|
48
44
|
}),
|
|
49
45
|
],
|
package/dist/lib/crypto/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { concatenate } from '../utils/array.js';
|
|
2
2
|
import { sodium } from '../sodium.js';
|
|
3
|
+
function dataTooShortError() {
|
|
4
|
+
return {
|
|
5
|
+
name: 'ClientError',
|
|
6
|
+
code: 'DATA_TOO_SHORT',
|
|
7
|
+
message: 'Encrypted payload is shorter than nonce + MAC',
|
|
8
|
+
};
|
|
9
|
+
}
|
|
3
10
|
export function encryptCryptoBox(data, publicKeyBob, privateKeyAlice) {
|
|
4
11
|
const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
|
|
5
12
|
const crypt = sodium.crypto_box_easy(data, nonce, sodium.from_hex(publicKeyBob), sodium.from_hex(privateKeyAlice));
|
|
@@ -10,7 +17,7 @@ export function generateCryptoBoxKeyPair() {
|
|
|
10
17
|
}
|
|
11
18
|
export function decryptCryptoBox(data, publicKeyAlice, privateKeyBob) {
|
|
12
19
|
if (data.length < sodium.crypto_box_NONCEBYTES + sodium.crypto_box_MACBYTES) {
|
|
13
|
-
throw
|
|
20
|
+
throw dataTooShortError();
|
|
14
21
|
}
|
|
15
22
|
const nonce = data.slice(0, sodium.crypto_box_NONCEBYTES);
|
|
16
23
|
const cipher = data.slice(sodium.crypto_box_NONCEBYTES);
|
|
@@ -27,7 +34,7 @@ export function encryptSecretBox(data, key) {
|
|
|
27
34
|
export function decryptSecretBox(data, key) {
|
|
28
35
|
if (data.length <
|
|
29
36
|
sodium.crypto_secretbox_NONCEBYTES + sodium.crypto_secretbox_MACBYTES) {
|
|
30
|
-
throw
|
|
37
|
+
throw dataTooShortError();
|
|
31
38
|
}
|
|
32
39
|
const nonce = data.slice(0, sodium.crypto_secretbox_NONCEBYTES);
|
|
33
40
|
const cipher = data.slice(sodium.crypto_secretbox_NONCEBYTES);
|
package/dist/lib/error/client.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export const clientErrorCodeKey = z.enum([
|
|
2
|
+
export const clientErrorCodeKey = z.enum([
|
|
3
|
+
'NOT_FOUND',
|
|
4
|
+
'DATA_TOO_SHORT',
|
|
5
|
+
'POPUP_BLOCKED',
|
|
6
|
+
'POPUP_CLOSED',
|
|
7
|
+
'SERVER_ENVIRONMENT',
|
|
8
|
+
]);
|
|
3
9
|
export const clientError = z.object({
|
|
4
10
|
name: z.literal('ClientError'),
|
|
5
11
|
code: clientErrorCodeKey,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
|
|
2
|
+
import { initSecrecy, login as secrecyLogin } from '../client/helpers.js';
|
|
3
|
+
const SecrecyContext = createContext(null);
|
|
4
|
+
export function SecrecyProvider(props) {
|
|
5
|
+
const { appId, session, redirect, secrecyUrls, children } = props;
|
|
6
|
+
const [client, setClient] = useState(null);
|
|
7
|
+
const [status, setStatus] = useState('idle');
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
const secrecyUrlsKey = JSON.stringify(secrecyUrls ?? null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let cancelled = false;
|
|
12
|
+
setStatus('loading');
|
|
13
|
+
initSecrecy({ appId, session, redirect, secrecyUrls })
|
|
14
|
+
.then((result) => {
|
|
15
|
+
if (cancelled)
|
|
16
|
+
return;
|
|
17
|
+
setClient(result);
|
|
18
|
+
setStatus(result !== null ? 'authenticated' : 'idle');
|
|
19
|
+
})
|
|
20
|
+
.catch((err) => {
|
|
21
|
+
if (cancelled)
|
|
22
|
+
return;
|
|
23
|
+
setError(err);
|
|
24
|
+
setStatus('error');
|
|
25
|
+
});
|
|
26
|
+
return () => {
|
|
27
|
+
cancelled = true;
|
|
28
|
+
};
|
|
29
|
+
// secrecyUrls is tracked through secrecyUrlsKey, a stable primitive.
|
|
30
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
31
|
+
}, [appId, session, redirect, secrecyUrlsKey]);
|
|
32
|
+
const login = useCallback(async () => {
|
|
33
|
+
setStatus('loading');
|
|
34
|
+
setError(null);
|
|
35
|
+
try {
|
|
36
|
+
const result = await secrecyLogin({
|
|
37
|
+
appId,
|
|
38
|
+
session,
|
|
39
|
+
secrecyUrls,
|
|
40
|
+
forceLogin: true,
|
|
41
|
+
});
|
|
42
|
+
setClient(result);
|
|
43
|
+
setStatus('authenticated');
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
setError(err);
|
|
47
|
+
setStatus('error');
|
|
48
|
+
}
|
|
49
|
+
// secrecyUrls is tracked through secrecyUrlsKey, a stable primitive.
|
|
50
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
|
+
}, [appId, session, secrecyUrlsKey]);
|
|
52
|
+
const logout = useCallback(async () => {
|
|
53
|
+
if (client === null) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await client.logout();
|
|
57
|
+
setClient(null);
|
|
58
|
+
setStatus('idle');
|
|
59
|
+
}, [client]);
|
|
60
|
+
const value = useMemo(() => ({ client, status, error, login, logout }), [client, status, error, login, logout]);
|
|
61
|
+
return createElement(SecrecyContext.Provider, { value }, children);
|
|
62
|
+
}
|
|
63
|
+
export function useSecrecy() {
|
|
64
|
+
const ctx = useContext(SecrecyContext);
|
|
65
|
+
if (ctx === null) {
|
|
66
|
+
throw new Error('useSecrecy() must be used within a <SecrecyProvider>');
|
|
67
|
+
}
|
|
68
|
+
return ctx;
|
|
69
|
+
}
|
|
@@ -10,7 +10,6 @@ const defaultOptions = {
|
|
|
10
10
|
scrollbars: 'no',
|
|
11
11
|
centered: true,
|
|
12
12
|
};
|
|
13
|
-
let popupCount = 1;
|
|
14
13
|
/**
|
|
15
14
|
* Return options converted to a string
|
|
16
15
|
*
|
|
@@ -23,12 +22,11 @@ function optionsToString(options) {
|
|
|
23
22
|
.join(',');
|
|
24
23
|
}
|
|
25
24
|
/**
|
|
26
|
-
* Get
|
|
25
|
+
* Get an unpredictable, unique name on each call
|
|
27
26
|
* @return {String}
|
|
28
27
|
*/
|
|
29
28
|
function defaultPopupName() {
|
|
30
|
-
|
|
31
|
-
return `Popup ${popupCount}`;
|
|
29
|
+
return `secrecy-${crypto.randomUUID()}`;
|
|
32
30
|
}
|
|
33
31
|
/**
|
|
34
32
|
* Convert "centered: true" key into concrete left and top arguments
|
|
@@ -101,6 +99,7 @@ function popupExecute(execute, url, name, options, callback) {
|
|
|
101
99
|
};
|
|
102
100
|
const optionsString = optionsToString(popupOptions);
|
|
103
101
|
const win = execute(url, popupName, optionsString);
|
|
102
|
+
const expectedOrigin = new URL(url, window.location.href).origin;
|
|
104
103
|
let isMessageSent = false;
|
|
105
104
|
let interval;
|
|
106
105
|
function popupCallbackOnce(err, data) {
|
|
@@ -110,7 +109,11 @@ function popupExecute(execute, url, name, options, callback) {
|
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
111
|
function onMessage(message) {
|
|
113
|
-
|
|
112
|
+
if (message === undefined || message.origin !== expectedOrigin) {
|
|
113
|
+
console.error('Invalid message origin', message?.origin, expectedOrigin);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const data = message.data;
|
|
114
117
|
if (data !== undefined && data.id === popupName) {
|
|
115
118
|
popupCallbackOnce(undefined, data.data);
|
|
116
119
|
window.removeEventListener('message', onMessage);
|
|
@@ -123,13 +126,21 @@ function popupExecute(execute, url, name, options, callback) {
|
|
|
123
126
|
if (win === null || win.closed) {
|
|
124
127
|
setTimeout(function delayWindowClosing() {
|
|
125
128
|
clearInterval(interval);
|
|
126
|
-
popupCallbackOnce(
|
|
129
|
+
popupCallbackOnce({
|
|
130
|
+
name: 'ClientError',
|
|
131
|
+
code: 'POPUP_CLOSED',
|
|
132
|
+
message: 'The popup was closed before completing',
|
|
133
|
+
});
|
|
127
134
|
}, 500);
|
|
128
135
|
}
|
|
129
136
|
}, 100);
|
|
130
137
|
}
|
|
131
138
|
else {
|
|
132
|
-
popupCallbackOnce(
|
|
139
|
+
popupCallbackOnce({
|
|
140
|
+
name: 'ClientError',
|
|
141
|
+
code: 'POPUP_BLOCKED',
|
|
142
|
+
message: 'The popup was blocked by the browser',
|
|
143
|
+
});
|
|
133
144
|
}
|
|
134
145
|
return win;
|
|
135
146
|
}
|
|
@@ -170,11 +181,8 @@ export function popupWithPost(url, postData, name, options, callback) {
|
|
|
170
181
|
}
|
|
171
182
|
/**
|
|
172
183
|
* Return html that when executed, will trigger the popup to callback with a response
|
|
173
|
-
*
|
|
174
|
-
* @param {Object}
|
|
175
|
-
* @return {String}
|
|
176
184
|
*/
|
|
177
|
-
export function popupResponse(id, data) {
|
|
185
|
+
export function popupResponse(id, data, targetOrigin) {
|
|
178
186
|
const jsonData = JSON.stringify({ id, data });
|
|
179
|
-
return `<script>window.opener.postMessage(${jsonData},
|
|
187
|
+
return `<script>window.opener.postMessage(${jsonData}, ${JSON.stringify(targetOrigin)});setTimeout(function() { window.close(); }, 50);</script>`;
|
|
180
188
|
}
|
|
@@ -12,6 +12,8 @@ export type BaseClientOptions = {
|
|
|
12
12
|
apiClient?: ApiClient;
|
|
13
13
|
onAccessDenied?: () => void | Promise<void>;
|
|
14
14
|
secrecyUrls?: Partial<SecrecyUrls>;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
reloadOnAccessDenied?: boolean;
|
|
15
17
|
};
|
|
16
18
|
export declare class BaseClient {
|
|
17
19
|
#private;
|
package/dist/types/cache.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import type { InternalNode, InternalData, InternalNodeFull, LocalData, InternalMinimalNodeForEncryption } from './client/types/index.js';
|
|
2
2
|
import { LRUCache } from 'lru-cache';
|
|
3
3
|
type NodeForEncryptionCached = InternalNode | InternalNodeFull | InternalMinimalNodeForEncryption;
|
|
4
|
-
export declare const dataCache:
|
|
5
|
-
export declare const nodesCache:
|
|
6
|
-
export declare const nodesEncryptionCache:
|
|
4
|
+
export declare const dataCache: LRUCache<string, InternalData, unknown>;
|
|
5
|
+
export declare const nodesCache: LRUCache<string, InternalNode | InternalNodeFull, unknown>;
|
|
6
|
+
export declare const nodesEncryptionCache: LRUCache<string, InternalMinimalNodeForEncryption, unknown>;
|
|
7
7
|
export declare const getNodeForEncryptionFromCache: (id: string) => NodeForEncryptionCached | undefined;
|
|
8
|
-
export declare const usersCache:
|
|
8
|
+
export declare const usersCache: LRUCache<string, {
|
|
9
9
|
firstname: string;
|
|
10
10
|
lastname: string;
|
|
11
11
|
id: string;
|
|
12
12
|
avatar: string | null;
|
|
13
13
|
isSearchable: boolean;
|
|
14
|
-
}>;
|
|
15
|
-
export declare const publicKeysCache:
|
|
14
|
+
}, unknown>;
|
|
15
|
+
export declare const publicKeysCache: LRUCache<string, string, unknown>;
|
|
16
16
|
export declare const dataContentCache: LRUCache<string, LocalData, unknown>;
|
|
17
|
+
export declare function clearAllCaches(): void;
|
|
17
18
|
export {};
|
|
@@ -13,9 +13,23 @@ export declare class SecrecyMailClient {
|
|
|
13
13
|
deletedMails({ mailType, }: {
|
|
14
14
|
mailType: ApiMail['type'];
|
|
15
15
|
}): Promise<Mail[]>;
|
|
16
|
+
create(data: NewMail & {
|
|
17
|
+
customMessage?: string | null | undefined;
|
|
18
|
+
}): Promise<SentMail>;
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated Pass a single object instead:
|
|
21
|
+
* `create({ ...data, customMessage })`.
|
|
22
|
+
*/
|
|
16
23
|
create(data: NewMail, customMessage?: string | null | undefined): Promise<SentMail>;
|
|
17
24
|
waitingReceivedMails(): Promise<WaitingReceivedMail[]>;
|
|
18
|
-
updateDraft(
|
|
25
|
+
updateDraft(input: {
|
|
26
|
+
draftId: string;
|
|
27
|
+
} & Partial<NewMail>): Promise<DraftMail>;
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated Pass a single object instead:
|
|
30
|
+
* `updateDraft({ draftId, ...args })`.
|
|
31
|
+
*/
|
|
32
|
+
updateDraft(draftId: string, args: Partial<NewMail>): Promise<DraftMail>;
|
|
19
33
|
deleteDraft(draftId: string): Promise<boolean>;
|
|
20
34
|
deleteTrash({ ids }: {
|
|
21
35
|
ids: string[];
|
|
@@ -24,6 +38,14 @@ export declare class SecrecyMailClient {
|
|
|
24
38
|
delete({ mailId }: {
|
|
25
39
|
mailId: string;
|
|
26
40
|
}): Promise<boolean>;
|
|
41
|
+
sendDraft(input: {
|
|
42
|
+
draftId: string;
|
|
43
|
+
customMessage?: string | null | undefined;
|
|
44
|
+
}): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* @deprecated Pass a single object instead:
|
|
47
|
+
* `sendDraft({ draftId, customMessage })`.
|
|
48
|
+
*/
|
|
27
49
|
sendDraft(draftId: string, customMessage?: string | null | undefined): Promise<boolean>;
|
|
28
50
|
sendWaitingEmails(): Promise<void>;
|
|
29
51
|
createDraft({ body, subject, senderFiles, recipients, replyToId, }: NewMail): Promise<DraftMail>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SecrecyClient } from './index.js';
|
|
2
2
|
import { type SecrecyUserApp } from './types/index.js';
|
|
3
3
|
import type { SecrecyUrls } from '../base-client.js';
|
|
4
|
+
export declare function assertBrowser(fn: string): void;
|
|
4
5
|
export declare function parseInfos(): SecrecyUserApp | null;
|
|
5
6
|
export interface HashInfos {
|
|
6
7
|
appId?: string;
|
|
@@ -29,4 +30,16 @@ type LoginResponse<Params extends UseSecrecyParams> = Params extends {
|
|
|
29
30
|
redirect: true;
|
|
30
31
|
} ? SecrecyClient | null : SecrecyClient;
|
|
31
32
|
export declare function login<Params extends UseSecrecyParams>({ appId, context, path, redirect, scopes, backPath, session, secrecyUrls, forceLogin, }: Params): Promise<LoginResponse<Params>>;
|
|
33
|
+
/**
|
|
34
|
+
* Single async entry point: runs `setup()`, reuses an existing session if
|
|
35
|
+
* one is found, and falls back to `login()` otherwise. Equivalent to
|
|
36
|
+
* `await setup()` + `getSecrecyClient()` + `login()`, without having to
|
|
37
|
+
* juggle the sync/async mismatch between them.
|
|
38
|
+
*/
|
|
39
|
+
export declare function initSecrecy(opts: {
|
|
40
|
+
appId: string;
|
|
41
|
+
session?: boolean | undefined;
|
|
42
|
+
redirect?: boolean | undefined;
|
|
43
|
+
secrecyUrls?: Partial<SecrecyUrls>;
|
|
44
|
+
}): Promise<SecrecyClient | null>;
|
|
32
45
|
export {};
|
|
@@ -3,7 +3,6 @@ import type { SecretStreamProgress } from '../crypto/data.js';
|
|
|
3
3
|
import { SecrecyCloudClient } from './SecrecyCloudClient.js';
|
|
4
4
|
import { SecrecyMailClient } from './SecrecyMailClient.js';
|
|
5
5
|
import { SecrecyAppClient } from './SecrecyAppClient.js';
|
|
6
|
-
import { SecrecyDbClient } from './SecrecyDbClient.js';
|
|
7
6
|
import { SecrecyWalletClient } from './SecrecyWalletClient.js';
|
|
8
7
|
import { SecrecyPayClient } from './SecrecyPayClient.js';
|
|
9
8
|
import { type ApiClient, type RouterInputs } from '../client.js';
|
|
@@ -22,15 +21,18 @@ export interface SecrecyClientOptions {
|
|
|
22
21
|
uaJwt: string;
|
|
23
22
|
apiClient?: ApiClient;
|
|
24
23
|
secrecyUrls?: Partial<SecrecyUrls>;
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
reloadOnAccessDenied?: boolean;
|
|
25
26
|
}
|
|
26
27
|
export declare class SecrecyClient extends BaseClient {
|
|
27
28
|
#private;
|
|
28
29
|
cloud: SecrecyCloudClient;
|
|
29
30
|
mail: SecrecyMailClient;
|
|
30
31
|
app: SecrecyAppClient;
|
|
31
|
-
db: SecrecyDbClient;
|
|
32
32
|
organization: SecrecyOrganizationClient;
|
|
33
|
+
/** @experimental Susceptible de changer sans version majeure. */
|
|
33
34
|
wallet: SecrecyWalletClient;
|
|
35
|
+
/** @experimental Susceptible de changer sans version majeure. */
|
|
34
36
|
pay: SecrecyPayClient;
|
|
35
37
|
user: SecrecyUserClient;
|
|
36
38
|
pseudonym: SecrecyPseudonymClient;
|
package/dist/types/client.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface CreateTrpcClientOptions {
|
|
|
8
8
|
session?: string | null | undefined;
|
|
9
9
|
apiUrl?: string | null | undefined;
|
|
10
10
|
onAccessDenied?: () => void | Promise<void> | null | undefined;
|
|
11
|
+
timeoutMs?: number | undefined;
|
|
11
12
|
}
|
|
12
13
|
export declare const createTRPCClient: (opts: CreateTrpcClientOptions) => import("@trpc/client").TRPCClient<import("@trpc/server").TRPCBuiltRouter<{
|
|
13
14
|
ctx: any;
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const clientErrorCodeKey: z.ZodEnum<{
|
|
3
3
|
NOT_FOUND: "NOT_FOUND";
|
|
4
|
+
DATA_TOO_SHORT: "DATA_TOO_SHORT";
|
|
5
|
+
POPUP_BLOCKED: "POPUP_BLOCKED";
|
|
6
|
+
POPUP_CLOSED: "POPUP_CLOSED";
|
|
7
|
+
SERVER_ENVIRONMENT: "SERVER_ENVIRONMENT";
|
|
4
8
|
}>;
|
|
5
9
|
export type CLIENT_ERROR_CODE_KEY = z.infer<typeof clientErrorCodeKey>;
|
|
6
10
|
export declare const clientError: z.ZodObject<{
|
|
7
11
|
name: z.ZodLiteral<"ClientError">;
|
|
8
12
|
code: z.ZodEnum<{
|
|
9
13
|
NOT_FOUND: "NOT_FOUND";
|
|
14
|
+
DATA_TOO_SHORT: "DATA_TOO_SHORT";
|
|
15
|
+
POPUP_BLOCKED: "POPUP_BLOCKED";
|
|
16
|
+
POPUP_CLOSED: "POPUP_CLOSED";
|
|
17
|
+
SERVER_ENVIRONMENT: "SERVER_ENVIRONMENT";
|
|
10
18
|
}>;
|
|
11
19
|
message: z.ZodString;
|
|
12
20
|
}, z.core.$strip>;
|
|
@@ -24,6 +24,10 @@ export declare const secrecyError: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
24
24
|
name: z.ZodLiteral<"ClientError">;
|
|
25
25
|
code: z.ZodEnum<{
|
|
26
26
|
NOT_FOUND: "NOT_FOUND";
|
|
27
|
+
DATA_TOO_SHORT: "DATA_TOO_SHORT";
|
|
28
|
+
POPUP_BLOCKED: "POPUP_BLOCKED";
|
|
29
|
+
POPUP_CLOSED: "POPUP_CLOSED";
|
|
30
|
+
SERVER_ENVIRONMENT: "SERVER_ENVIRONMENT";
|
|
27
31
|
}>;
|
|
28
32
|
message: z.ZodString;
|
|
29
33
|
}, z.core.$strip>], "name">;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { SecrecyClient } from '../client/index.js';
|
|
3
|
+
import type { SecrecyUrls } from '../base-client.js';
|
|
4
|
+
export type SecrecyStatus = 'idle' | 'loading' | 'authenticated' | 'error';
|
|
5
|
+
export interface SecrecyContextValue {
|
|
6
|
+
client: SecrecyClient | null;
|
|
7
|
+
status: SecrecyStatus;
|
|
8
|
+
error: unknown;
|
|
9
|
+
login: () => Promise<void>;
|
|
10
|
+
logout: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface SecrecyProviderProps {
|
|
13
|
+
appId: string;
|
|
14
|
+
session?: boolean | undefined;
|
|
15
|
+
redirect?: boolean | undefined;
|
|
16
|
+
secrecyUrls?: Partial<SecrecyUrls> | undefined;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
export declare function SecrecyProvider(props: SecrecyProviderProps): ReactNode;
|
|
20
|
+
export declare function useSecrecy(): SecrecyContextValue;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TS Rewrite of https://github.com/enhancv/popup-tools
|
|
3
3
|
*/
|
|
4
|
+
import { type ClientError } from '../error/client.js';
|
|
4
5
|
type YesNo = 'yes' | 'no';
|
|
5
6
|
interface Options {
|
|
6
7
|
width?: number;
|
|
@@ -13,7 +14,7 @@ interface Options {
|
|
|
13
14
|
left?: number;
|
|
14
15
|
top?: number;
|
|
15
16
|
}
|
|
16
|
-
type Callback = (err?:
|
|
17
|
+
type Callback = (err?: ClientError, data?: unknown) => void;
|
|
17
18
|
/**
|
|
18
19
|
* Open a popup using the first argument.
|
|
19
20
|
* Wait for it to close and call the callback.
|
|
@@ -44,9 +45,6 @@ export declare function popup(url: string, name: string, options: Partial<Option
|
|
|
44
45
|
export declare function popupWithPost(url: string, postData: Record<string, string>, name: string, options: Partial<Options>, callback: Callback): Window | null;
|
|
45
46
|
/**
|
|
46
47
|
* Return html that when executed, will trigger the popup to callback with a response
|
|
47
|
-
*
|
|
48
|
-
* @param {Object}
|
|
49
|
-
* @return {String}
|
|
50
48
|
*/
|
|
51
|
-
export declare function popupResponse<T>(id: string, data: T): string;
|
|
49
|
+
export declare function popupResponse<T>(id: string, data: T, targetOrigin: string): string;
|
|
52
50
|
export {};
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@secrecy/lib",
|
|
3
3
|
"author": "Anonymize <anonymize@gmail.com>",
|
|
4
|
-
"description": "
|
|
5
|
-
"version": "1.86.
|
|
4
|
+
"description": "Client-side end-to-end encryption SDK for Secrecy",
|
|
5
|
+
"version": "1.86.1-feat-improve-sdk.2",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.21.1"
|
|
8
|
+
},
|
|
6
9
|
"repository": {
|
|
7
10
|
"type": "git",
|
|
8
11
|
"url": "https://github.com/anonymize-org/lib.git"
|
|
@@ -13,11 +16,12 @@
|
|
|
13
16
|
},
|
|
14
17
|
"homepage": "https://github.com/anonymize-org/lib#readme",
|
|
15
18
|
"keywords": [
|
|
16
|
-
"anonymize",
|
|
17
|
-
"lib",
|
|
18
19
|
"secrecy",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
20
|
+
"e2ee",
|
|
21
|
+
"encryption",
|
|
22
|
+
"libsodium",
|
|
23
|
+
"zero-knowledge",
|
|
24
|
+
"browser"
|
|
21
25
|
],
|
|
22
26
|
"type": "module",
|
|
23
27
|
"exports": {
|
|
@@ -51,9 +55,12 @@
|
|
|
51
55
|
"devDependencies": {
|
|
52
56
|
"@commitlint/cli": "^20.3.0",
|
|
53
57
|
"@commitlint/config-conventional": "^20.3.0",
|
|
58
|
+
"@happy-dom/global-registrator": "^20.11.2",
|
|
54
59
|
"@prisma/client": "6.17.1",
|
|
60
|
+
"@trpc/server": "^11.6.0",
|
|
55
61
|
"@types/bun": "^1.3.5",
|
|
56
62
|
"@types/jsonwebtoken": "^9.0.10",
|
|
63
|
+
"@types/react": "^19.2.18",
|
|
57
64
|
"@types/spark-md5": "^3.0.5",
|
|
58
65
|
"@types/streamsaver": "^2.0.5",
|
|
59
66
|
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
|
@@ -67,6 +74,7 @@
|
|
|
67
74
|
"husky": "^9.1.7",
|
|
68
75
|
"npm-run-all": "^4.1.5",
|
|
69
76
|
"prettier": "^3.7.4",
|
|
77
|
+
"react": "^19.2.8",
|
|
70
78
|
"rimraf": "^6.1.2",
|
|
71
79
|
"semantic-release": "^25.0.2",
|
|
72
80
|
"typedoc": "^0.28.15",
|
|
@@ -78,7 +86,6 @@
|
|
|
78
86
|
"@js-temporal/polyfill": "^0.5.1",
|
|
79
87
|
"@secrecy/trpc-api-types": "1.41.0-dev.1",
|
|
80
88
|
"@trpc/client": "11.6.0",
|
|
81
|
-
"@trpc/server": "^11.6.0",
|
|
82
89
|
"@types/libsodium-wrappers-sumo": "^0.7.8",
|
|
83
90
|
"axios": "^1.13.2",
|
|
84
91
|
"bson": "^7.0.0",
|
|
@@ -92,5 +99,13 @@
|
|
|
92
99
|
"streamsaver": "^2.0.6",
|
|
93
100
|
"superjson": "2.2.6",
|
|
94
101
|
"zod": "4.3.5"
|
|
102
|
+
},
|
|
103
|
+
"peerDependencies": {
|
|
104
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
105
|
+
},
|
|
106
|
+
"peerDependenciesMeta": {
|
|
107
|
+
"react": {
|
|
108
|
+
"optional": true
|
|
109
|
+
}
|
|
95
110
|
}
|
|
96
111
|
}
|