Hide "become a sustainer" button if you're already a sustainer
This commit is contained in:
parent
7edf3763a8
commit
67b17ec317
22 changed files with 318 additions and 26 deletions
|
@ -441,9 +441,10 @@ message SyncMessage {
|
|||
|
||||
message FetchLatest {
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
LOCAL_PROFILE = 1;
|
||||
STORAGE_MANIFEST = 2;
|
||||
UNKNOWN = 0;
|
||||
LOCAL_PROFILE = 1;
|
||||
STORAGE_MANIFEST = 2;
|
||||
SUBSCRIPTION_STATUS = 3;
|
||||
}
|
||||
|
||||
optional Type type = 1;
|
||||
|
|
|
@ -137,4 +137,7 @@ message AccountRecord {
|
|||
optional bool primarySendsSms = 18;
|
||||
optional string e164 = 19;
|
||||
repeated string preferredReactionEmoji = 20;
|
||||
optional bytes subscriberId = 21;
|
||||
optional string subscriberCurrencyCode = 22;
|
||||
optional bool displayBadgesOnProfile = 23;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ import { initializeAllJobQueues } from './jobs/initializeAllJobQueues';
|
|||
import { removeStorageKeyJobQueue } from './jobs/removeStorageKeyJobQueue';
|
||||
import { ourProfileKeyService } from './services/ourProfileKey';
|
||||
import { notificationService } from './services/notifications';
|
||||
import { areWeASubscriberService } from './services/areWeASubscriber';
|
||||
import { shouldRespondWithProfileKey } from './util/shouldRespondWithProfileKey';
|
||||
import { LatestQueue } from './util/LatestQueue';
|
||||
import { parseIntOrThrow } from './util/parseIntOrThrow';
|
||||
|
@ -346,6 +347,8 @@ export async function startApp(): Promise<void> {
|
|||
onlineEventTarget: window,
|
||||
storage: window.storage,
|
||||
});
|
||||
|
||||
areWeASubscriberService.update(window.storage, server);
|
||||
});
|
||||
|
||||
const eventHandlerQueue = new window.PQueue({
|
||||
|
@ -3477,6 +3480,11 @@ export async function startApp(): Promise<void> {
|
|||
log.info('onFetchLatestSync: fetching latest manifest');
|
||||
await window.Signal.Services.runStorageServiceSyncJob();
|
||||
break;
|
||||
case FETCH_LATEST_ENUM.SUBSCRIPTION_STATUS:
|
||||
log.info('onFetchLatestSync: fetching latest subscription status');
|
||||
strictAssert(server, 'WebAPI not ready');
|
||||
areWeASubscriberService.update(window.storage, server);
|
||||
break;
|
||||
default:
|
||||
log.info(`onFetchLatestSync: Unknown type encountered ${eventType}`);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ const i18n = setupI18n('en', enMessages);
|
|||
const story = storiesOf('Components/BadgeDialog', module);
|
||||
|
||||
const defaultProps: ComponentProps<typeof BadgeDialog> = {
|
||||
areWeASubscriber: false,
|
||||
badges: getFakeBadges(3),
|
||||
firstName: 'Alice',
|
||||
i18n,
|
||||
|
@ -95,3 +96,7 @@ story.add('Five badges', () => (
|
|||
story.add('Many badges', () => (
|
||||
<BadgeDialog {...defaultProps} badges={getFakeBadges(50)} />
|
||||
));
|
||||
|
||||
story.add('Many badges, user is a subscriber', () => (
|
||||
<BadgeDialog {...defaultProps} areWeASubscriber badges={getFakeBadges(50)} />
|
||||
));
|
||||
|
|
|
@ -16,6 +16,7 @@ import { BadgeCarouselIndex } from './BadgeCarouselIndex';
|
|||
import { BadgeSustainerInstructionsDialog } from './BadgeSustainerInstructionsDialog';
|
||||
|
||||
type PropsType = Readonly<{
|
||||
areWeASubscriber: boolean;
|
||||
badges: ReadonlyArray<BadgeType>;
|
||||
firstName?: string;
|
||||
i18n: LocalizerType;
|
||||
|
@ -53,6 +54,7 @@ export function BadgeDialog(props: PropsType): null | JSX.Element {
|
|||
}
|
||||
|
||||
function BadgeDialogWithBadges({
|
||||
areWeASubscriber,
|
||||
badges,
|
||||
firstName,
|
||||
i18n,
|
||||
|
@ -114,17 +116,19 @@ function BadgeDialogWithBadges({
|
|||
title={title}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className={classNames(
|
||||
'BadgeDialog__instructions-button',
|
||||
currentBadge.category !== BadgeCategory.Donor &&
|
||||
'BadgeDialog__instructions-button--hidden'
|
||||
)}
|
||||
onClick={onShowInstructions}
|
||||
size={ButtonSize.Large}
|
||||
>
|
||||
{i18n('BadgeDialog__become-a-sustainer-button')}
|
||||
</Button>
|
||||
{!areWeASubscriber && (
|
||||
<Button
|
||||
className={classNames(
|
||||
'BadgeDialog__instructions-button',
|
||||
currentBadge.category !== BadgeCategory.Donor &&
|
||||
'BadgeDialog__instructions-button--hidden'
|
||||
)}
|
||||
onClick={onShowInstructions}
|
||||
size={ButtonSize.Large}
|
||||
>
|
||||
{i18n('BadgeDialog__become-a-sustainer-button')}
|
||||
</Button>
|
||||
)}
|
||||
<BadgeCarouselIndex
|
||||
currentIndex={currentBadgeIndex}
|
||||
totalCount={badges.length}
|
||||
|
|
|
@ -29,6 +29,7 @@ const defaultContact: ConversationType = getDefaultConversation({
|
|||
});
|
||||
|
||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||
areWeASubscriber: false,
|
||||
areWeAdmin: boolean('areWeAdmin', overrideProps.areWeAdmin || false),
|
||||
badges: overrideProps.badges || [],
|
||||
contact: overrideProps.contact || defaultContact,
|
||||
|
|
|
@ -16,6 +16,7 @@ import { SharedGroupNames } from '../SharedGroupNames';
|
|||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||
|
||||
export type PropsDataType = {
|
||||
areWeASubscriber: boolean;
|
||||
areWeAdmin: boolean;
|
||||
badges: ReadonlyArray<BadgeType>;
|
||||
contact?: ConversationType;
|
||||
|
@ -50,6 +51,7 @@ enum ContactModalView {
|
|||
}
|
||||
|
||||
export const ContactModal = ({
|
||||
areWeASubscriber,
|
||||
areWeAdmin,
|
||||
badges,
|
||||
contact,
|
||||
|
@ -219,6 +221,7 @@ export const ContactModal = ({
|
|||
case ContactModalView.ShowingBadges:
|
||||
return (
|
||||
<BadgeDialog
|
||||
areWeASubscriber={areWeASubscriber}
|
||||
badges={badges}
|
||||
firstName={contact.firstName}
|
||||
i18n={i18n}
|
||||
|
|
|
@ -37,6 +37,7 @@ const createProps = (hasGroupLink = false, expireTimer?: number): Props => ({
|
|||
addMembers: async () => {
|
||||
action('addMembers');
|
||||
},
|
||||
areWeASubscriber: false,
|
||||
canEditGroupInfo: false,
|
||||
candidateContactsToAdd: times(10, () => getDefaultConversation()),
|
||||
conversation: expireTimer
|
||||
|
|
|
@ -55,6 +55,7 @@ enum ModalState {
|
|||
|
||||
export type StateProps = {
|
||||
addMembers: (conversationIds: ReadonlyArray<string>) => Promise<void>;
|
||||
areWeASubscriber: boolean;
|
||||
badges?: ReadonlyArray<BadgeType>;
|
||||
canEditGroupInfo: boolean;
|
||||
candidateContactsToAdd: Array<ConversationType>;
|
||||
|
@ -109,6 +110,7 @@ export type Props = StateProps & ActionProps;
|
|||
|
||||
export const ConversationDetails: React.ComponentType<Props> = ({
|
||||
addMembers,
|
||||
areWeASubscriber,
|
||||
badges,
|
||||
canEditGroupInfo,
|
||||
candidateContactsToAdd,
|
||||
|
@ -316,6 +318,7 @@ export const ConversationDetails: React.ComponentType<Props> = ({
|
|||
)}
|
||||
|
||||
<ConversationDetailsHeader
|
||||
areWeASubscriber={areWeASubscriber}
|
||||
badges={badges}
|
||||
canEdit={canEditGroupInfo}
|
||||
conversation={conversation}
|
||||
|
|
|
@ -41,6 +41,7 @@ const Wrapper = (overrideProps: Partial<Props>) => {
|
|||
|
||||
return (
|
||||
<ConversationDetailsHeader
|
||||
areWeASubscriber={false}
|
||||
conversation={createConversation()}
|
||||
i18n={i18n}
|
||||
canEdit={false}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { BadgeDialog } from '../../BadgeDialog';
|
|||
import type { BadgeType } from '../../../badges/types';
|
||||
|
||||
export type Props = {
|
||||
areWeASubscriber: boolean;
|
||||
badges?: ReadonlyArray<BadgeType>;
|
||||
canEdit: boolean;
|
||||
conversation: ConversationType;
|
||||
|
@ -36,6 +37,7 @@ enum ConversationDetailsHeaderActiveModal {
|
|||
const bem = bemGenerator('ConversationDetails-header');
|
||||
|
||||
export const ConversationDetailsHeader: React.ComponentType<Props> = ({
|
||||
areWeASubscriber,
|
||||
badges,
|
||||
canEdit,
|
||||
conversation,
|
||||
|
@ -128,6 +130,7 @@ export const ConversationDetailsHeader: React.ComponentType<Props> = ({
|
|||
case ConversationDetailsHeaderActiveModal.ShowingBadges:
|
||||
modal = (
|
||||
<BadgeDialog
|
||||
areWeASubscriber={areWeASubscriber}
|
||||
badges={badges || []}
|
||||
firstName={conversation.firstName}
|
||||
i18n={i18n}
|
||||
|
|
36
ts/services/areWeASubscriber.ts
Normal file
36
ts/services/areWeASubscriber.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { StorageInterface } from '../types/Storage.d';
|
||||
import type { WebAPIType } from '../textsecure/WebAPI';
|
||||
import { LatestQueue } from '../util/LatestQueue';
|
||||
import { waitForOnline } from '../util/waitForOnline';
|
||||
|
||||
// This is only exported for testing.
|
||||
export class AreWeASubscriberService {
|
||||
private readonly queue = new LatestQueue();
|
||||
|
||||
update(
|
||||
storage: Pick<StorageInterface, 'get' | 'put' | 'onready'>,
|
||||
server: Pick<WebAPIType, 'getHasSubscription'>
|
||||
): void {
|
||||
this.queue.add(async () => {
|
||||
await new Promise<void>(resolve => storage.onready(resolve));
|
||||
|
||||
const subscriberId = storage.get('subscriberId');
|
||||
if (!subscriberId || !subscriberId.byteLength) {
|
||||
storage.put('areWeASubscriber', false);
|
||||
return;
|
||||
}
|
||||
|
||||
await waitForOnline(navigator, window);
|
||||
|
||||
storage.put(
|
||||
'areWeASubscriber',
|
||||
await server.getHasSubscription(subscriberId)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const areWeASubscriberService = new AreWeASubscriberService();
|
|
@ -293,6 +293,19 @@ export async function toAccountRecord(
|
|||
);
|
||||
|
||||
accountRecord.pinnedConversations = pinnedConversations;
|
||||
|
||||
const subscriberId = window.storage.get('subscriberId');
|
||||
if (subscriberId instanceof Uint8Array) {
|
||||
accountRecord.subscriberId = subscriberId;
|
||||
}
|
||||
const subscriberCurrencyCode = window.storage.get('subscriberCurrencyCode');
|
||||
if (typeof subscriberCurrencyCode === 'string') {
|
||||
accountRecord.subscriberCurrencyCode = subscriberCurrencyCode;
|
||||
}
|
||||
accountRecord.displayBadgesOnProfile = Boolean(
|
||||
window.storage.get('displayBadgesOnProfile')
|
||||
);
|
||||
|
||||
applyUnknownFields(accountRecord, conversation);
|
||||
|
||||
return accountRecord;
|
||||
|
@ -845,6 +858,9 @@ export async function mergeAccountRecord(
|
|||
universalExpireTimer,
|
||||
e164: accountE164,
|
||||
preferredReactionEmoji: rawPreferredReactionEmoji,
|
||||
subscriberId,
|
||||
subscriberCurrencyCode,
|
||||
displayBadgesOnProfile,
|
||||
} = accountRecord;
|
||||
|
||||
window.storage.put('read-receipt-setting', Boolean(readReceipts));
|
||||
|
@ -1018,6 +1034,14 @@ export async function mergeAccountRecord(
|
|||
window.storage.put('pinnedConversationIds', remotelyPinnedConversationIds);
|
||||
}
|
||||
|
||||
if (subscriberId instanceof Uint8Array) {
|
||||
window.storage.put('subscriberId', subscriberId);
|
||||
}
|
||||
if (typeof subscriberCurrencyCode === 'string') {
|
||||
window.storage.put('subscriberCurrencyCode', subscriberCurrencyCode);
|
||||
}
|
||||
window.storage.put('displayBadgesOnProfile', Boolean(displayBadgesOnProfile));
|
||||
|
||||
const ourID = window.ConversationController.getOurConversationId();
|
||||
|
||||
if (!ourID) {
|
||||
|
|
|
@ -772,6 +772,7 @@ async function removeAllSignedPreKeys() {
|
|||
const ITEM_KEYS: Partial<Record<ItemKeyType, Array<string>>> = {
|
||||
senderCertificate: ['value.serialized'],
|
||||
senderCertificateNoE164: ['value.serialized'],
|
||||
subscriberId: ['value'],
|
||||
profileKey: ['value'],
|
||||
};
|
||||
async function createOrUpdateItem<K extends ItemKeyType>(data: ItemType<K>) {
|
||||
|
|
|
@ -36,6 +36,8 @@ export type ItemsStateType = {
|
|||
readonly preferredLeftPaneWidth?: number;
|
||||
|
||||
readonly preferredReactionEmoji?: Array<string>;
|
||||
|
||||
readonly areWeASubscriber?: boolean;
|
||||
};
|
||||
|
||||
// Actions
|
||||
|
|
|
@ -20,6 +20,12 @@ const DEFAULT_PREFERRED_LEFT_PANE_WIDTH = 320;
|
|||
|
||||
export const getItems = (state: StateType): ItemsStateType => state.items;
|
||||
|
||||
export const getAreWeASubscriber = createSelector(
|
||||
getItems,
|
||||
({ areWeASubscriber }: Readonly<ItemsStateType>): boolean =>
|
||||
Boolean(areWeASubscriber)
|
||||
);
|
||||
|
||||
export const getUserAgent = createSelector(
|
||||
getItems,
|
||||
(state: ItemsStateType): string => state.userAgent as string
|
||||
|
|
|
@ -7,6 +7,7 @@ import type { PropsDataType } from '../../components/conversation/ContactModal';
|
|||
import { ContactModal } from '../../components/conversation/ContactModal';
|
||||
import type { StateType } from '../reducer';
|
||||
|
||||
import { getAreWeASubscriber } from '../selectors/items';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import { getBadgesSelector } from '../selectors/badges';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
|
@ -35,6 +36,7 @@ const mapStateToProps = (state: StateType): PropsDataType => {
|
|||
}
|
||||
|
||||
return {
|
||||
areWeASubscriber: getAreWeASubscriber(state),
|
||||
areWeAdmin,
|
||||
badges: getBadgesSelector(state)(contact.badges),
|
||||
contact,
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
getConversationByUuidSelector,
|
||||
} from '../selectors/conversations';
|
||||
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
||||
import { getAreWeASubscriber } from '../selectors/items';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import type { MediaItemType } from '../../types/MediaItem';
|
||||
import {
|
||||
|
@ -82,6 +83,7 @@ const mapStateToProps = (
|
|||
|
||||
return {
|
||||
...props,
|
||||
areWeASubscriber: getAreWeASubscriber(state),
|
||||
badges,
|
||||
canEditGroupInfo,
|
||||
candidateContactsToAdd,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
import {
|
||||
getAreWeASubscriber,
|
||||
getEmojiSkinTone,
|
||||
getPinnedConversationIds,
|
||||
getPreferredLeftPaneWidth,
|
||||
|
@ -21,6 +22,21 @@ describe('both/state/selectors/items', () => {
|
|||
} as any;
|
||||
}
|
||||
|
||||
describe('#getAreWeASubscriber', () => {
|
||||
it('returns false if the value is not in storage', () => {
|
||||
assert.isFalse(getAreWeASubscriber(getRootState({})));
|
||||
});
|
||||
|
||||
it('returns the value in storage', () => {
|
||||
assert.isFalse(
|
||||
getAreWeASubscriber(getRootState({ areWeASubscriber: false }))
|
||||
);
|
||||
assert.isTrue(
|
||||
getAreWeASubscriber(getRootState({ areWeASubscriber: true }))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getEmojiSkinTone', () => {
|
||||
it('returns 0 if passed anything invalid', () => {
|
||||
[
|
||||
|
|
130
ts/test-electron/services/areWeASubscriber_test.ts
Normal file
130
ts/test-electron/services/areWeASubscriber_test.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { AreWeASubscriberService } from '../../services/areWeASubscriber';
|
||||
import { explodePromise } from '../../util/explodePromise';
|
||||
|
||||
describe('"are we a subscriber?" service', () => {
|
||||
const subscriberId = new Uint8Array([1, 2, 3]);
|
||||
const fakeStorageDefaults = {
|
||||
onready: sinon.stub().callsArg(0),
|
||||
get: sinon.stub().withArgs('subscriberId').returns(subscriberId),
|
||||
};
|
||||
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
let service: AreWeASubscriberService;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
|
||||
service = new AreWeASubscriberService();
|
||||
sandbox.stub(navigator, 'onLine').get(() => true);
|
||||
});
|
||||
|
||||
it("stores false if there's no local subscriber ID", done => {
|
||||
const fakeServer = { getHasSubscription: sandbox.stub() };
|
||||
const fakeStorage = {
|
||||
...fakeStorageDefaults,
|
||||
get: () => undefined,
|
||||
put: sandbox.stub().callsFake((key, value) => {
|
||||
assert.strictEqual(key, 'areWeASubscriber');
|
||||
assert.isFalse(value);
|
||||
done();
|
||||
}),
|
||||
};
|
||||
|
||||
service.update(fakeStorage, fakeServer);
|
||||
});
|
||||
|
||||
it("doesn't make a network request if there's no local subscriber ID", done => {
|
||||
const fakeServer = { getHasSubscription: sandbox.stub() };
|
||||
const fakeStorage = {
|
||||
...fakeStorageDefaults,
|
||||
get: () => undefined,
|
||||
put: sandbox.stub().callsFake(() => {
|
||||
sinon.assert.notCalled(fakeServer.getHasSubscription);
|
||||
done();
|
||||
}),
|
||||
};
|
||||
|
||||
service.update(fakeStorage, fakeServer);
|
||||
});
|
||||
|
||||
it('requests the subscriber ID from the server', done => {
|
||||
const fakeServer = { getHasSubscription: sandbox.stub().resolves(false) };
|
||||
const fakeStorage = {
|
||||
...fakeStorageDefaults,
|
||||
put: sandbox
|
||||
.stub()
|
||||
.withArgs('areWeASubscriber')
|
||||
.callsFake(() => {
|
||||
sinon.assert.calledWithExactly(
|
||||
fakeServer.getHasSubscription,
|
||||
subscriberId
|
||||
);
|
||||
done();
|
||||
}),
|
||||
};
|
||||
|
||||
service.update(fakeStorage, fakeServer);
|
||||
});
|
||||
|
||||
it("stores when we're not a subscriber", done => {
|
||||
const fakeServer = { getHasSubscription: sandbox.stub().resolves(false) };
|
||||
const fakeStorage = {
|
||||
...fakeStorageDefaults,
|
||||
put: sandbox.stub().callsFake((key, value) => {
|
||||
assert.strictEqual(key, 'areWeASubscriber');
|
||||
assert.isFalse(value);
|
||||
done();
|
||||
}),
|
||||
};
|
||||
|
||||
service.update(fakeStorage, fakeServer);
|
||||
});
|
||||
|
||||
it("stores when we're a subscriber", done => {
|
||||
const fakeServer = { getHasSubscription: sandbox.stub().resolves(true) };
|
||||
const fakeStorage = {
|
||||
...fakeStorageDefaults,
|
||||
put: sandbox.stub().callsFake((key, value) => {
|
||||
assert.strictEqual(key, 'areWeASubscriber');
|
||||
assert.isTrue(value);
|
||||
done();
|
||||
}),
|
||||
};
|
||||
|
||||
service.update(fakeStorage, fakeServer);
|
||||
});
|
||||
|
||||
it('only runs one request at a time and enqueues one other', async () => {
|
||||
const allDone = explodePromise<void>();
|
||||
let putCallCount = 0;
|
||||
|
||||
const fakeServer = { getHasSubscription: sandbox.stub().resolves(false) };
|
||||
const fakeStorage = {
|
||||
...fakeStorageDefaults,
|
||||
put: sandbox.stub().callsFake(() => {
|
||||
putCallCount += 1;
|
||||
if (putCallCount === 2) {
|
||||
allDone.resolve();
|
||||
} else if (putCallCount > 2) {
|
||||
throw new Error('Unexpected call to storage put');
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
service.update(fakeStorage, fakeServer);
|
||||
service.update(fakeStorage, fakeServer);
|
||||
service.update(fakeStorage, fakeServer);
|
||||
service.update(fakeStorage, fakeServer);
|
||||
service.update(fakeStorage, fakeServer);
|
||||
|
||||
await allDone.promise;
|
||||
|
||||
sinon.assert.calledTwice(fakeServer.getHasSubscription);
|
||||
sinon.assert.calledTwice(fakeStorage.put);
|
||||
});
|
||||
});
|
|
@ -26,6 +26,7 @@ import Long from 'long';
|
|||
import type { Readable } from 'stream';
|
||||
|
||||
import { assert, strictAssert } from '../util/assert';
|
||||
import { isRecord } from '../util/isRecord';
|
||||
import * as durations from '../util/durations';
|
||||
import { getUserAgent } from '../util/getUserAgent';
|
||||
import { getStreamWithTimeout } from '../util/getStreamWithTimeout';
|
||||
|
@ -169,7 +170,6 @@ type RedactUrl = (url: string) => string;
|
|||
|
||||
type PromiseAjaxOptionsType = {
|
||||
socketManager?: SocketManager;
|
||||
accessKey?: string;
|
||||
basicAuth?: string;
|
||||
certificateAuthority?: string;
|
||||
contentType?: string;
|
||||
|
@ -191,12 +191,20 @@ type PromiseAjaxOptionsType = {
|
|||
stack?: string;
|
||||
timeout?: number;
|
||||
type: HTTPCodeType;
|
||||
unauthenticated?: boolean;
|
||||
user?: string;
|
||||
validateResponse?: any;
|
||||
version: string;
|
||||
abortSignal?: AbortSignal;
|
||||
};
|
||||
} & (
|
||||
| {
|
||||
unauthenticated?: false;
|
||||
accessKey?: string;
|
||||
}
|
||||
| {
|
||||
unauthenticated: true;
|
||||
accessKey: undefined | string;
|
||||
}
|
||||
);
|
||||
|
||||
type JSONWithDetailsType = {
|
||||
data: unknown;
|
||||
|
@ -321,13 +329,10 @@ async function _promiseAjax(
|
|||
if (basicAuth) {
|
||||
fetchOptions.headers.Authorization = `Basic ${basicAuth}`;
|
||||
} else if (unauthenticated) {
|
||||
if (!accessKey) {
|
||||
throw new Error(
|
||||
'_promiseAjax: mode is unauthenticated, but accessKey was not provided'
|
||||
);
|
||||
if (accessKey) {
|
||||
// Access key is already a Base64 string
|
||||
fetchOptions.headers['Unidentified-Access-Key'] = accessKey;
|
||||
}
|
||||
// Access key is already a Base64 string
|
||||
fetchOptions.headers['Unidentified-Access-Key'] = accessKey;
|
||||
} else if (options.user && options.password) {
|
||||
const auth = Bytes.toBase64(
|
||||
Bytes.fromString(`${options.user}:${options.password}`)
|
||||
|
@ -542,6 +547,7 @@ const URL_CALLS = {
|
|||
storageModify: 'v1/storage/',
|
||||
storageRead: 'v1/storage/read',
|
||||
storageToken: 'v1/storage/auth',
|
||||
subscriptions: 'v1/subscription',
|
||||
supportUnauthenticatedDelivery: 'v1/devices/unauthenticated_delivery',
|
||||
updateDeviceName: 'v1/accounts/name',
|
||||
username: 'v1/accounts/username',
|
||||
|
@ -608,7 +614,6 @@ export type MessageType = Readonly<{
|
|||
}>;
|
||||
|
||||
type AjaxOptionsType = {
|
||||
accessKey?: string;
|
||||
basicAuth?: string;
|
||||
call: keyof typeof URL_CALLS;
|
||||
contentType?: string;
|
||||
|
@ -622,11 +627,19 @@ type AjaxOptionsType = {
|
|||
responseType?: 'json' | 'bytes' | 'byteswithdetails' | 'stream';
|
||||
schema?: unknown;
|
||||
timeout?: number;
|
||||
unauthenticated?: boolean;
|
||||
urlParameters?: string;
|
||||
username?: string;
|
||||
validateResponse?: any;
|
||||
};
|
||||
} & (
|
||||
| {
|
||||
unauthenticated?: false;
|
||||
accessKey?: string;
|
||||
}
|
||||
| {
|
||||
unauthenticated: true;
|
||||
accessKey: undefined | string;
|
||||
}
|
||||
);
|
||||
|
||||
export type WebAPIConnectOptionsType = WebAPICredentials & {
|
||||
useWebSocket?: boolean;
|
||||
|
@ -753,6 +766,7 @@ export type WebAPIType = {
|
|||
getAttachment: (cdnKey: string, cdnNumber?: number) => Promise<Uint8Array>;
|
||||
getAvatar: (path: string) => Promise<Uint8Array>;
|
||||
getDevices: () => Promise<GetDevicesResultType>;
|
||||
getHasSubscription: (subscriberId: Uint8Array) => Promise<boolean>;
|
||||
getGroup: (options: GroupCredentialsType) => Promise<Proto.Group>;
|
||||
getGroupFromLink: (
|
||||
inviteLinkPassword: string,
|
||||
|
@ -1092,6 +1106,7 @@ export function initialize({
|
|||
getGroupExternalCredential,
|
||||
getGroupFromLink,
|
||||
getGroupLog,
|
||||
getHasSubscription,
|
||||
getIceServers,
|
||||
getKeysForIdentifier,
|
||||
getKeysForIdentifierUnauth,
|
||||
|
@ -2493,6 +2508,27 @@ export function initialize({
|
|||
};
|
||||
}
|
||||
|
||||
async function getHasSubscription(
|
||||
subscriberId: Uint8Array
|
||||
): Promise<boolean> {
|
||||
const formattedId = toWebSafeBase64(Bytes.toBase64(subscriberId));
|
||||
const data = await _ajax({
|
||||
call: 'subscriptions',
|
||||
httpType: 'GET',
|
||||
urlParameters: `/${formattedId}`,
|
||||
responseType: 'json',
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
redactUrl: _createRedactor(formattedId),
|
||||
});
|
||||
|
||||
return (
|
||||
isRecord(data) &&
|
||||
isRecord(data.subscription) &&
|
||||
Boolean(data.subscription.active)
|
||||
);
|
||||
}
|
||||
|
||||
function getProvisioningResource(
|
||||
handler: IRequestHandler
|
||||
): Promise<WebSocketResource> {
|
||||
|
|
4
ts/types/Storage.d.ts
vendored
4
ts/types/Storage.d.ts
vendored
|
@ -134,6 +134,10 @@ export type StorageAccessType = {
|
|||
paymentAddress: string;
|
||||
zoomFactor: ZoomFactorType;
|
||||
preferredLeftPaneWidth: number;
|
||||
areWeASubscriber: boolean;
|
||||
subscriberId: Uint8Array;
|
||||
subscriberCurrencyCode: string;
|
||||
displayBadgesOnProfile: boolean;
|
||||
|
||||
// Deprecated
|
||||
senderCertificateWithUuid: never;
|
||||
|
|
Loading…
Reference in a new issue