Compare commits

...
Author SHA1 Message Date
Sivin VargheseandGitHub 54308a4145 Merge branch 'develop' into fix/editor-heading-error 2024-01-29 20:23:00 +05:30
Shivam MishraandGitHub 2eeec22868 fix: Cookies.set does not stringify JSON (#8807)
* fix: cookie setting

* chore: remove debug statement

* chore: add specs to test stringify
2024-01-29 18:14:49 +05:30
Muhsin KelothandGitHub ef50edb9e2 feat: Standardise the external channel user id and user name (#8802)
* feat: Standardize the external channel id and user name

* chore: add specs

* chore: add name space `social`
2024-01-29 16:57:10 +05:30
Shivam MishraandGitHub 3ed80fa867 feat: expiry for cache keys [CW-3038] (#8793)
* feat: set cache keys for 3 days only

* feat: invalidate should set the latest timestamp

* refactor: cache_keys concern

* remove invalidate_cache method
* refactor reset to set to new value instead of delete
* ensure only one event is dispatched

* feat: set expiry to 24 hours

* chore: make expiry 48 hours

* feat: include destroy event

* feat: set expiry to 72 days

* fix: typo

* test: cache update after `touch`

* test: update cache keys

* refactor: remove after_touch, it's already handled in commit
2024-01-29 15:27:26 +05:30
Nithin David ThomasandGitHub 88f44c21f2 Merge branch 'develop' into fix/editor-heading-error 2024-01-29 01:23:51 -08:00
Sivin VargheseandGitHub cf664ca2a0 feat: Inbox item card component (#8801) 2024-01-29 14:52:55 +05:30
Muhsin KelothandGitHub 485c561b18 feat: Add attributes location and country_code to Contact model (#8803)
feat: Add attributes location and country_code to Contact model
2024-01-29 14:10:14 +05:30
Pranav Raj SandGitHub 766698cb3a chore: Upgrade @june-so/analytics-next, js-cookie to the latest version (#8799)
This is a small change, upgrading 2 packages to the latest version. getJSON is removed from the latest version, I've added a patch for the same across the codebase.

fixes: https://linear.app/chatwoot/issue/CW-3035/upgrade-dependencies
2024-01-29 11:41:42 +04:00
Nithin David Thomas feb8ec7879 Upgrades package ith latest fixes 2024-01-29 10:33:23 +05:30
Shivam MishraandGitHub 082793290a feat: allow multiple files for FB and Insta [CW-3019] (#8783)
* feat: allow multiple messages

* fix: typo

* feat: send content and attachments both

* refactor: message sequence

* test: multiple attachments for instagram

* test: multiple attachments on facebook
2024-01-28 13:20:54 +05:30
Nithin David Thomas b5e81ccd3b Updates yarn lock 2024-01-26 10:45:16 +05:30
Nithin David ThomasandGitHub b31040901e Merge branch 'develop' into fix/editor-heading-error 2024-01-25 20:02:16 -08:00
Sivin VargheseandGitHub 1f610c7fc5 Merge branch 'develop' into fix/editor-heading-error 2024-01-24 11:09:20 +05:30
Nithin David Thomas 188a8a67c0 chore: Updates prosemirror package to latest version 2024-01-23 08:37:06 +05:30
31 changed files with 484 additions and 108 deletions
+3 -2
View File
@@ -29,11 +29,12 @@ export default {
return fetchPromise;
},
hasAuthCookie() {
return !!Cookies.getJSON('cw_d_session_info');
return !!Cookies.get('cw_d_session_info');
},
getAuthData() {
if (this.hasAuthCookie()) {
return Cookies.getJSON('cw_d_session_info');
const savedAuthInfo = Cookies.get('cw_d_session_info');
return JSON.parse(savedAuthInfo || '{}');
}
return false;
},
@@ -0,0 +1,70 @@
<script setup>
// import { defineProps } from 'vue';
import PriorityIcon from './components/PriorityIcon.vue';
import StatusIcon from './components/StatusIcon.vue';
import InboxNameAndId from './components/InboxNameAndId.vue';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
// const props = defineProps({
// notificationItem: {
// type: Object,
// default: () => {},
// },
// });
const assigneeMeta = {
thumbnail: '',
name: 'Michael Johnson',
};
const { thumbnail, name } = assigneeMeta || {};
const status = 'open';
const priority = 'high';
const inboxTypeMessage = 'Mentioned by Michael';
const inboxMessage = 'What is the best way to get started?';
const inbox = {
inbox_id: 16787,
inbox_name: 'Chatwoot Support',
};
</script>
<template>
<div
class="flex max-w-[360px] flex-col pl-5 pr-3 gap-2.5 py-3 w-full bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-500 hover:bg-slate-25 dark:hover:bg-slate-800 cursor-pointer"
>
<div class="flex relative items-center justify-between w-full">
<div
class="absolute -left-3.5 flex w-2 h-2 rounded bg-woot-500 dark:bg-woot-500"
/>
<InboxNameAndId :inbox="inbox" />
<div class="flex gap-2">
<PriorityIcon :priority="priority" />
<StatusIcon :status="status" />
</div>
</div>
<div class="flex flex-row justify-between items-center w-full">
<div class="flex gap-1.5 items-center max-w-[80%]">
<Thumbnail
v-if="assigneeMeta"
:src="thumbnail"
:username="name"
size="20px"
/>
<div class="flex min-w-0">
<span
class="font-medium text-slate-800 dark:text-slate-100 text-xs overflow-hidden text-ellipsis whitespace-nowrap"
>
{{ inboxTypeMessage }}<span v-if="inboxTypeMessage">:</span>
<span class="font-normal">{{ inboxMessage }}</span>
</span>
</div>
</div>
<span
class="font-medium text-slate-600 dark:text-slate-300 text-xs whitespace-nowrap"
>
10h ago
</span>
</div>
</div>
</template>
@@ -0,0 +1,34 @@
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
inbox: {
type: Object,
default: () => {},
},
});
const { inbox } = props;
const { inbox_id: inboxId, inbox_name: inboxName } = inbox;
</script>
<template>
<div
class="inline-flex items-center rounded-[4px] border border-slate-100 dark:border-slate-600 divide-x divide-slate-100 dark:divide-slate-600 bg-none"
>
<div class="flex items-center gap-0.5 py-0.5 px-1.5">
<fluent-icon
class="text-slate-600 dark:text-slate-300"
icon="globe-desktop"
size="14"
/>
<span class="font-medium text-slate-600 dark:text-slate-300 text-xs">
{{ inboxName }}
</span>
</div>
<div class="flex items-center py-0.5 px-1.5">
<span class="font-medium text-slate-600 dark:text-slate-300 text-xs">
{{ inboxId }}
</span>
</div>
</div>
</template>
@@ -0,0 +1,75 @@
<script setup>
import { CONVERSATION_PRIORITY } from 'shared/constants/messages';
import { defineProps } from 'vue';
const props = defineProps({
priority: {
type: String,
default: '',
},
});
</script>
<template>
<div class="inline-flex items-center justify-center rounded-md">
<!-- High Priority -->
<svg
v-if="props.priority === CONVERSATION_PRIORITY.HIGH"
class="h-4 w-4"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="4" y="12" width="4" height="8" rx="1" fill="#FFC291" />
<rect x="10" y="8" width="4" height="12" rx="1" fill="#FFC291" />
<rect x="16" y="4" width="4" height="16" rx="1" fill="#FFC291" />
</svg>
<!-- Low Priority -->
<svg
v-if="props.priority === CONVERSATION_PRIORITY.LOW"
class="h-4 w-4"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="4" y="12" width="4" height="8" rx="1" fill="#FFC291" />
<rect x="10" y="8" width="4" height="12" rx="1" fill="#DDDDDD" />
<rect x="16" y="4" width="4" height="16" rx="1" fill="#DDDDDD" />
</svg>
<!-- Medium Priority -->
<svg
v-if="props.priority === CONVERSATION_PRIORITY.MEDIUM"
class="h-4 w-4"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="4" y="12" width="4" height="8" rx="1" fill="#FFC291" />
<rect x="10" y="8" width="4" height="12" rx="1" fill="#FFC291" />
<rect x="16" y="4" width="4" height="16" rx="1" fill="#DDDDDD" />
</svg>
<!-- Urgent Priority -->
<svg
v-if="props.priority === CONVERSATION_PRIORITY.URGENT"
class="h-4 w-4"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="4" y="12" width="4" height="8" rx="1" fill="#E5484D" />
<rect x="10" y="8" width="4" height="12" rx="1" fill="#E5484D" />
<rect x="16" y="4" width="4" height="16" rx="1" fill="#E5484D" />
</svg>
</div>
</template>
@@ -0,0 +1,80 @@
<script setup>
import { CONVERSATION_STATUS } from 'shared/constants/messages';
import { defineProps } from 'vue';
const props = defineProps({
status: {
type: String,
default: '',
},
});
</script>
<template>
<div class="inline-flex items-center justify-center rounded-md">
<!-- Pending -->
<svg
v-if="props.status === CONVERSATION_STATUS.PENDING"
class="h-3.5 w-3.5"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.1 0.0449978V1.8558C4.5486 2.2986 1.8 5.328 1.8 9C1.8 12.9762 5.0238 16.2 9 16.2C10.6641 16.2 12.195 15.6357 13.4154 14.688L14.6961 15.9687C13.1445 17.2377 11.16 18 9 18C4.0293 18 0 13.9707 0 9C0 4.3335 3.5523 0.495898 8.1 0.0449978ZM17.955 9.9C17.775 11.7099 17.0604 13.3623 15.9687 14.6952L14.688 13.4154C15.462 12.4191 15.9804 11.2149 16.1442 9.9H17.9559H17.955ZM9.9018 0.0449978C14.1534 0.467098 17.5338 3.8484 17.9568 8.1H16.1451C15.7392 4.8438 13.158 2.2626 9.9018 1.8558V0.0440979V0.0449978Z"
class="fill-[#B9BBC6]"
/>
</svg>
<!-- Open -->
<svg
v-if="props.status === CONVERSATION_STATUS.OPEN"
class="h-3.5 w-3.5"
width="19"
height="19"
viewBox="0 0 19 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.375 18.875C4.19733 18.875 0 14.6776 0 9.5C0 4.32233 4.19733 0.125 9.375 0.125C14.5526 0.125 18.75 4.32233 18.75 9.5C18.75 14.6776 14.5526 18.875 9.375 18.875ZM9.375 17C13.5172 17 16.875 13.6422 16.875 9.5C16.875 5.35786 13.5172 2 9.375 2C5.23286 2 1.875 5.35786 1.875 9.5C1.875 13.6422 5.23286 17 9.375 17Z"
class="fill-[#ED8A5C]"
/>
</svg>
<!-- Snoozed -->
<svg
v-if="props.status === CONVERSATION_STATUS.SNOOZED"
class="h-3.5 w-3.5"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 18C4.0293 18 0 13.9707 0 9C0 4.0293 4.0293 0 9 0C13.9707 0 18 4.0293 18 9C18 13.9707 13.9707 18 9 18ZM2.9961 12.9825C3.58766 13.8676 4.36812 14.6105 5.28129 15.1577C6.19446 15.7049 7.21761 16.0428 8.27707 16.147C9.33652 16.2513 10.4059 16.1193 11.4082 15.7606C12.4105 15.4019 13.3208 14.8254 14.0736 14.0726C14.8263 13.3198 15.4027 12.4094 15.7613 11.4071C16.12 10.4047 16.2518 9.33532 16.1475 8.27588C16.0431 7.21644 15.7052 6.19332 15.1579 5.2802C14.6106 4.36707 13.8676 3.58668 12.9825 2.9952C13.3706 4.3796 13.383 5.84237 13.0186 7.23318C12.6542 8.62399 11.926 9.89269 10.9089 10.9089C9.89277 11.9258 8.62423 12.6539 7.23359 13.0183C5.84296 13.3828 4.38037 13.3704 2.9961 12.9825Z"
class="fill-[#0B68CB]"
/>
</svg>
<!-- Resolved -->
<svg
v-if="props.status === CONVERSATION_STATUS.RESOLVED"
class="h-3.5 w-3.5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="3 3 17.92 17.92"
>
<path
d="M11.96 20.92C7.01152 20.92 3 16.9084 3 11.96C3 7.01152 7.01152 3 11.96 3C16.9084 3 20.92 7.01152 20.92 11.96C20.92 16.9084 16.9084 20.92 11.96 20.92ZM11.96 19.128C15.9188 19.128 19.128 15.9188 19.128 11.96C19.128 8.00122 15.9188 4.792 11.96 4.792C8.00122 4.792 4.792 8.00122 4.792 11.96C4.792 15.9188 8.00122 19.128 11.96 19.128Z"
class="fill-[#5BB98C]"
/>
<path
d="M11.9599 17.9333C15.2589 17.9333 17.9332 15.2589 17.9332 11.96C17.9332 8.66098 15.2589 5.98663 11.9599 5.98663C8.66092 5.98663 5.98657 8.66098 5.98657 11.96C5.98657 15.2589 8.66092 17.9333 11.9599 17.9333Z"
class="fill-[#5BB98C]"
/>
</svg>
</div>
</template>
@@ -12,7 +12,7 @@ jest.mock('../../../utils/api', () => ({
getHeaderExpiry: jest.fn(),
}));
jest.mock('js-cookie', () => ({
getJSON: jest.fn(),
get: jest.fn(),
}));
const commit = jest.fn();
@@ -155,14 +155,14 @@ describe('#actions', () => {
describe('#setUser', () => {
it('sends correct actions if user is logged in', async () => {
Cookies.getJSON.mockImplementation(() => true);
Cookies.get.mockImplementation(() => true);
actions.setUser({ commit, dispatch });
expect(commit.mock.calls).toEqual([]);
expect(dispatch.mock.calls).toEqual([['validityCheck']]);
});
it('sends correct actions if user is not logged in', async () => {
Cookies.getJSON.mockImplementation(() => false);
Cookies.get.mockImplementation(() => false);
actions.setUser({ commit, dispatch });
expect(commit.mock.calls).toEqual([
[types.default.CLEAR_USER],
+1 -1
View File
@@ -27,7 +27,7 @@ export const getHeaderExpiry = response =>
export const setAuthCredentials = response => {
const expiryDate = getHeaderExpiry(response);
Cookies.set('cw_d_session_info', response.headers, {
Cookies.set('cw_d_session_info', JSON.stringify(response.headers), {
expires: differenceInDays(expiryDate, new Date()),
});
setUser(response.data.data, expiryDate);
+7
View File
@@ -34,5 +34,12 @@ export const setCookieWithDomain = (
domain: baseDomain,
};
// if type of value is object, stringify it
// this is because js-cookies 3.0 removed builtin json support
// ref: https://github.com/js-cookie/js-cookie/releases/tag/v3.0.0
if (typeof value === 'object') {
value = JSON.stringify(value);
}
Cookies.set(name, value, cookieOptions);
};
@@ -107,6 +107,26 @@ describe('setCookieWithDomain', () => {
);
});
it('should stringify the cookie value when setting', () => {
setCookieWithDomain(
'myCookie',
{ value: 'cookieValue' },
{
baseDomain: 'example.com',
}
);
expect(Cookies.set).toHaveBeenCalledWith(
'myCookie',
JSON.stringify({ value: 'cookieValue' }),
expect.objectContaining({
expires: 365,
sameSite: 'Lax',
domain: 'example.com',
})
);
});
it('should set a cookie with custom expiration, sameSite attribute, and specific base domain', () => {
setCookieWithDomain('myCookie', 'cookieValue', {
expires: 7,
+1 -1
View File
@@ -3,7 +3,7 @@ import { DEFAULT_REDIRECT_URL } from 'dashboard/constants/globals';
import { frontendURL } from 'dashboard/helper/URLHelper';
export const hasAuthCookie = () => {
return !!Cookies.getJSON('cw_d_session_info');
return !!Cookies.get('cw_d_session_info');
};
const getSSOAccountPath = ({ ssoAccountId, user }) => {
@@ -10,10 +10,10 @@ jest.mock('dashboard/store/utils/api', () => ({
jest.mock('../CommonHelper', () => ({ replaceRouteWithReload: jest.fn() }));
jest.mock('js-cookie', () => ({
getJSON: jest.fn(),
get: jest.fn(),
}));
Cookies.getJSON.mockReturnValueOnce(true).mockReturnValue(false);
Cookies.get.mockReturnValueOnce(true).mockReturnValue(false);
describe('#validateRouteAccess', () => {
it('reset cookies and continues to the login page if the SSO parameters are present', () => {
validateRouteAccess(
@@ -40,7 +40,7 @@ describe('#validateRouteAccess', () => {
});
it('redirects to dashboard if auth cookie is present', () => {
Cookies.getJSON.mockImplementation(() => true);
Cookies.get.mockImplementation(() => true);
validateRouteAccess({ name: 'login' }, next);
expect(clearBrowserSessionCookies).not.toHaveBeenCalled();
expect(replaceRouteWithReload).toHaveBeenCalledWith('/app/');
@@ -2,7 +2,7 @@ module AccountCacheRevalidator
extend ActiveSupport::Concern
included do
after_commit :update_account_cache, on: [:create, :update]
after_commit :update_account_cache, on: [:create, :update, :destroy]
end
def update_account_cache
+11 -9
View File
@@ -4,6 +4,8 @@ module CacheKeys
include CacheKeysHelper
include Events::Types
CACHE_KEYS_EXPIRY = 72.hours
included do
class_attribute :cacheable_models
self.cacheable_models = [Label, Inbox, Team]
@@ -18,26 +20,26 @@ module CacheKeys
keys
end
def invalidate_cache_key_for(key)
prefixed_cache_key = get_prefixed_cache_key(id, key)
Redis::Alfred.delete(prefixed_cache_key)
dispatch_cache_update_event
end
def update_cache_key(key)
prefixed_cache_key = get_prefixed_cache_key(id, key)
Redis::Alfred.set(prefixed_cache_key, Time.now.utc.to_i)
update_cache_key_for_account(id, key)
dispatch_cache_update_event
end
def reset_cache_keys
self.class.cacheable_models.each do |model|
invalidate_cache_key_for(model.name.underscore)
update_cache_key_for_account(id, model.name.underscore)
end
dispatch_cache_update_event
end
private
def update_cache_key_for_account(account_id, key)
prefixed_cache_key = get_prefixed_cache_key(account_id, key)
Redis::Alfred.setex(prefixed_cache_key, Time.now.utc.to_i, CACHE_KEYS_EXPIRY)
end
def dispatch_cache_update_event
Rails.configuration.dispatcher.dispatch(ACCOUNT_CACHE_INVALIDATED, Time.zone.now, cache_keys: cache_keys, account: self)
end
+2
View File
@@ -7,11 +7,13 @@
# id :integer not null, primary key
# additional_attributes :jsonb
# contact_type :integer default("visitor")
# country_code :string default("")
# custom_attributes :jsonb
# email :string
# identifier :string
# last_activity_at :datetime
# last_name :string default("")
# location :string default("")
# middle_name :string default("")
# name :string default("")
# phone_number :string
@@ -7,7 +7,12 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService
def perform_reply
send_message_to_facebook fb_text_message_params if message.content.present?
send_message_to_facebook fb_attachment_message_params if message.attachments.present?
if message.attachments.present?
message.attachments.each do |attachment|
send_message_to_facebook fb_attachment_message_params(attachment)
end
end
rescue Facebook::Messenger::FacebookError => e
# TODO : handle specific errors or else page will get disconnected
handle_facebook_error(e)
@@ -41,8 +46,7 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService
"#{error_code} - #{error_message}"
end
def fb_attachment_message_params
attachment = message.attachments.first
def fb_attachment_message_params(attachment)
{
recipient: { id: contact.get_source_id(inbox.id) },
message: {
@@ -64,14 +68,6 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService
'file'
end
def fb_message_params
if message.attachments.blank?
fb_text_message_params
else
fb_attachment_message_params
end
end
def sent_first_outgoing_message_after_24_hours?
# we can send max 1 message after 24 hour window
conversation.messages.outgoing.where('id > ?', conversation.last_incoming_message.id).count == 1
@@ -14,8 +14,13 @@ class Instagram::SendOnInstagramService < Base::SendOnChannelService
end
def perform_reply
send_to_facebook_page attachament_message_params if message.attachments.present?
send_to_facebook_page message_params
if message.attachments.present?
message.attachments.each do |attachment|
send_to_facebook_page attachment_message_params(attachment)
end
end
send_to_facebook_page message_params if message.content.present?
rescue StandardError => e
ChatwootExceptionTracker.new(e, account: message.account, user: message.sender).capture_exception
# TODO : handle specific errors or else page will get disconnected
@@ -33,8 +38,7 @@ class Instagram::SendOnInstagramService < Base::SendOnChannelService
merge_human_agent_tag(params)
end
def attachament_message_params
attachment = message.attachments.first
def attachment_message_params(attachment)
params = {
recipient: { id: contact.get_source_id(inbox.id) },
message: {
@@ -24,7 +24,9 @@ class Instagram::WebhooksBaseService
def update_instagram_profile_link(user)
return unless user['username']
# TODO: Remove this once we show the social_instagram_user_name in the UI instead of the username
@contact.additional_attributes = @contact.additional_attributes.merge({ 'social_profiles': { 'instagram': user['username'] } })
@contact.additional_attributes = @contact.additional_attributes.merge({ 'social_instagram_user_name': user['username'] })
@contact.save
end
end
@@ -139,7 +139,14 @@ class Line::IncomingMessageService
def contact_attributes
{
name: line_contact_info['displayName'],
avatar_url: line_contact_info['pictureUrl']
avatar_url: line_contact_info['pictureUrl'],
additional_attributes: additional_attributes
}
end
def additional_attributes
{
social_line_user_id: line_contact_info['userId']
}
end
@@ -78,8 +78,11 @@ class Telegram::IncomingMessageService
def additional_attributes
{
# TODO: Remove this once we show the social_telegram_user_name in the UI instead of the username
username: telegram_params_username,
language_code: telegram_params_language_code
language_code: telegram_params_language_code,
social_telegram_user_id: telegram_params_from_id,
social_telegram_user_name: telegram_params_username
}
end
@@ -0,0 +1,6 @@
class AddLocationAndCountryCodeToContacts < ActiveRecord::Migration[7.0]
def change
add_column :contacts, :location, :string, default: ''
add_column :contacts, :country_code, :string, default: ''
end
end
+3 -1
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2024_01_24_084032) do
ActiveRecord::Schema[7.0].define(version: 2024_01_29_080827) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@@ -421,6 +421,8 @@ ActiveRecord::Schema[7.0].define(version: 2024_01_24_084032) do
t.integer "contact_type", default: 0
t.string "middle_name", default: ""
t.string "last_name", default: ""
t.string "location", default: ""
t.string "country_code", default: ""
t.index "lower((email)::text), account_id", name: "index_contacts_on_lower_email_account_id"
t.index ["account_id", "email", "phone_number", "identifier"], name: "index_contacts_on_nonempty_fields", where: "(((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))"
t.index ["account_id"], name: "index_contacts_on_account_id"
+3 -3
View File
@@ -31,10 +31,10 @@
],
"dependencies": {
"@braid/vue-formulate": "^2.5.2",
"@chatwoot/prosemirror-schema": "1.0.5",
"@chatwoot/prosemirror-schema": "1.0.7",
"@chatwoot/utils": "^0.0.21",
"@hcaptcha/vue-hcaptcha": "^0.3.2",
"@june-so/analytics-next": "^1.36.5",
"@june-so/analytics-next": "^2.0.0",
"@radix-ui/colors": "^1.0.1",
"@rails/actioncable": "6.1.3",
"@rails/ujs": "^7.0.3-1",
@@ -60,7 +60,7 @@
"highlight.js": "~10.4.1",
"idb": "^7.1.1",
"ionicons": "~2.0.1",
"js-cookie": "^2.2.1",
"js-cookie": "^3.0.5",
"libphonenumber-js": "^1.10.24",
"logrocket": "^3.0.1",
"logrocket-vuex": "^0.0.3",
@@ -43,10 +43,13 @@ RSpec.describe 'Super Admin accounts API', type: :request do
it 'shows the list of accounts' do
expect(account.cache_keys.keys).to contain_exactly(:inbox, :label, :team)
sign_in(super_admin, scope: :super_admin)
now_timestamp = Time.now.utc.to_i
post "/super_admin/accounts/#{account.id}/reset_cache"
expect(response).to have_http_status(:redirect)
expect(flash[:notice]).to eq('Cache keys cleared')
expect(account.reload.cache_keys.values.map(&:to_i)).to eq([0, 0, 0])
expect(account.reload.cache_keys.values.all? { |v| v.to_i == now_timestamp }).to be(true)
end
end
end
@@ -43,7 +43,7 @@ describe Webhooks::InstagramEventsJob do
instagram_inbox.reload
expect(instagram_inbox.contacts.count).to be 1
expect(instagram_inbox.contacts.last.additional_attributes['social_profiles']['instagram']).to eq 'some_user_name'
expect(instagram_inbox.contacts.last.additional_attributes['social_instagram_user_name']).to eq 'some_user_name'
expect(instagram_inbox.conversations.count).to be 1
expect(instagram_inbox.messages.count).to be 1
expect(instagram_inbox.messages.last.content_attributes['is_unsupported']).to be_nil
@@ -59,7 +59,7 @@ describe Webhooks::InstagramEventsJob do
instagram_inbox.reload
expect(instagram_inbox.contacts.count).to be 1
expect(instagram_inbox.contacts.last.additional_attributes['social_profiles']['instagram']).to eq 'some_user_name'
expect(instagram_inbox.contacts.last.additional_attributes['social_instagram_user_name']).to eq 'some_user_name'
expect(instagram_inbox.conversations.count).to be 1
expect(instagram_inbox.messages.count).to be 1
@@ -170,7 +170,7 @@ describe Webhooks::InstagramEventsJob do
instagram_inbox.reload
expect(instagram_inbox.contacts.count).to be 1
expect(instagram_inbox.contacts.last.additional_attributes['social_profiles']['instagram']).to eq 'some_user_name'
expect(instagram_inbox.contacts.last.additional_attributes['social_instagram_user_name']).to eq 'some_user_name'
expect(instagram_inbox.conversations.count).to be 1
expect(instagram_inbox.messages.count).to be 1
expect(instagram_inbox.messages.last.content_attributes['is_unsupported']).to be true
+15 -19
View File
@@ -14,6 +14,7 @@ RSpec.describe CacheKeys do
before do
allow(Redis::Alfred).to receive(:delete)
allow(Redis::Alfred).to receive(:set)
allow(Redis::Alfred).to receive(:setex)
allow(Rails.configuration.dispatcher).to receive(:dispatch)
end
@@ -27,28 +28,11 @@ RSpec.describe CacheKeys do
end
end
describe '#invalidate_cache_key_for' do
it 'deletes the cache key' do
test_model.invalidate_cache_key_for('label')
expect(Redis::Alfred).to have_received(:delete).with('idb-cache-key-account-1-label')
end
it 'dispatches a cache update event' do
test_model.invalidate_cache_key_for('label')
expect(Rails.configuration.dispatcher).to have_received(:dispatch).with(
CacheKeys::ACCOUNT_CACHE_INVALIDATED,
kind_of(ActiveSupport::TimeWithZone),
cache_keys: test_model.cache_keys,
account: test_model
)
end
end
describe '#update_cache_key' do
it 'updates the cache key' do
allow(Time).to receive(:now).and_return(Time.parse('2023-05-29 00:00:00 UTC'))
test_model.update_cache_key('label')
expect(Redis::Alfred).to have_received(:set).with('idb-cache-key-account-1-label', Time.now.utc.to_i)
expect(Redis::Alfred).to have_received(:setex).with('idb-cache-key-account-1-label', kind_of(Integer), CacheKeys::CACHE_KEYS_EXPIRY)
end
it 'dispatches a cache update event' do
@@ -66,8 +50,20 @@ RSpec.describe CacheKeys do
it 'invalidates all cache keys for cacheable models' do
test_model.reset_cache_keys
test_model.class.cacheable_models.each do |model|
expect(Redis::Alfred).to have_received(:delete).with("idb-cache-key-account-1-#{model.name.underscore}")
expect(Redis::Alfred).to have_received(:setex).with("idb-cache-key-account-1-#{model.name.underscore}", kind_of(Integer),
CacheKeys::CACHE_KEYS_EXPIRY)
end
end
it 'dispatches a cache update event' do
test_model.reset_cache_keys
expect(Rails.configuration.dispatcher).to have_received(:dispatch).with(
CacheKeys::ACCOUNT_CACHE_INVALIDATED,
kind_of(ActiveSupport::TimeWithZone),
cache_keys: test_model.cache_keys,
account: test_model
)
end
end
end
+10
View File
@@ -250,5 +250,15 @@ RSpec.describe Inbox do
cache_keys: inbox.account.cache_keys
)
end
it 'updates the cache key after update' do
expect(inbox.account).to receive(:update_cache_key).with('inbox')
inbox.update(name: 'New Name')
end
it 'updates the cache key after touch' do
expect(inbox.account).to receive(:update_cache_key).with('inbox')
inbox.touch # rubocop:disable Rails/SkipsModelValidations
end
end
end
@@ -90,6 +90,24 @@ describe Facebook::SendOnFacebookService do
}, { page_id: facebook_channel.page_id })
end
it 'if message is sent with multiple attachments' do
message = build(:message, content: nil, message_type: 'outgoing', inbox: facebook_inbox, account: account, conversation: conversation)
avatar = message.attachments.new(account_id: message.account_id, file_type: :image)
avatar.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
sample = message.attachments.new(account_id: message.account_id, file_type: :image)
sample.file.attach(io: Rails.root.join('spec/assets/sample.png').open, filename: 'sample.png', content_type: 'image/png')
message.save!
service = described_class.new(message: message)
# Stub the send_to_facebook_page method on the service instance
allow(service).to receive(:send_message_to_facebook)
service.perform
# Now you can set expectations on the stubbed method for each attachment
expect(service).to have_received(:send_message_to_facebook).exactly(:twice)
end
it 'if message sent from chatwoot is failed' do
message = create(:message, message_type: 'outgoing', inbox: facebook_inbox, account: account, conversation: conversation)
allow(bot).to receive(:deliver).and_return({ error: { message: 'Invalid OAuth access token.', type: 'OAuthException', code: 190,
@@ -50,7 +50,25 @@ describe Instagram::SendOnInstagramService do
response = described_class.new(message: message).perform
expect(response).to eq({ message_id: 'anyrandommessageid1234567890' })
expect(response).to eq({ message_id: 'anyrandommessageid1234567890' })
end
it 'if message is sent from chatwoot and is outgoing with multiple attachments' do
message = build(:message, content: nil, message_type: 'outgoing', inbox: instagram_inbox, account: account, conversation: conversation)
avatar = message.attachments.new(account_id: message.account_id, file_type: :image)
avatar.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
sample = message.attachments.new(account_id: message.account_id, file_type: :image)
sample.file.attach(io: Rails.root.join('spec/assets/sample.png').open, filename: 'sample.png', content_type: 'image/png')
message.save!
service = described_class.new(message: message)
# Stub the send_to_facebook_page method on the service instance
allow(service).to receive(:send_to_facebook_page)
service.perform
# Now you can set expectations on the stubbed method for each attachment
expect(service).to have_received(:send_to_facebook_page).exactly(:twice)
end
it 'if message with attachment is sent from chatwoot and is outgoing' do
@@ -157,6 +157,7 @@ describe Line::IncomingMessageService do
described_class.new(inbox: line_channel.inbox, params: params).perform
expect(line_channel.inbox.conversations).not_to eq(0)
expect(Contact.all.first.name).to eq('LINE Test')
expect(Contact.all.first.additional_attributes['social_line_user_id']).to eq('U4af4980629')
expect(line_channel.inbox.messages.first.content).to eq('Hello, world')
end
end
@@ -204,6 +205,7 @@ describe Line::IncomingMessageService do
described_class.new(inbox: line_channel.inbox, params: image_params).perform
expect(line_channel.inbox.conversations).not_to eq(0)
expect(Contact.all.first.name).to eq('LINE Test')
expect(Contact.all.first.additional_attributes['social_line_user_id']).to eq('U4af4980629')
expect(line_channel.inbox.messages.first.content).to be_nil
expect(line_channel.inbox.messages.first.attachments.first.file_type).to eq('image')
expect(line_channel.inbox.messages.first.attachments.first.file.blob.filename.to_s).to eq('media-354718.png')
@@ -233,6 +235,7 @@ describe Line::IncomingMessageService do
described_class.new(inbox: line_channel.inbox, params: video_params).perform
expect(line_channel.inbox.conversations).not_to eq(0)
expect(Contact.all.first.name).to eq('LINE Test')
expect(Contact.all.first.additional_attributes['social_line_user_id']).to eq('U4af4980629')
expect(line_channel.inbox.messages.first.content).to be_nil
expect(line_channel.inbox.messages.first.attachments.first.file_type).to eq('video')
expect(line_channel.inbox.messages.first.attachments.first.file.blob.filename.to_s).to eq('media-354718.mp4')
@@ -65,6 +65,8 @@ describe Telegram::IncomingMessageService do
described_class.new(inbox: telegram_channel.inbox, params: params).perform
expect(telegram_channel.inbox.conversations.count).not_to eq(0)
expect(Contact.all.first.name).to eq('Sojan Jose')
expect(Contact.all.first.additional_attributes['social_telegram_user_id']).to eq(23)
expect(Contact.all.first.additional_attributes['social_telegram_user_name']).to eq('sojan')
expect(telegram_channel.inbox.messages.first.content).to eq('test')
end
end
@@ -105,6 +107,8 @@ describe Telegram::IncomingMessageService do
described_class.new(inbox: telegram_channel.inbox, params: params).perform
expect(telegram_channel.inbox.conversations.count).not_to eq(0)
expect(Contact.all.first.name).to eq('Sojan Jose')
expect(Contact.all.first.additional_attributes['social_telegram_user_id']).to eq(23)
expect(Contact.all.first.additional_attributes['social_telegram_user_name']).to eq('sojan')
expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('audio')
end
end
@@ -277,6 +281,7 @@ describe Telegram::IncomingMessageService do
described_class.new(inbox: telegram_channel.inbox, params: params).perform
expect(telegram_channel.inbox.conversations.count).not_to eq(0)
expect(Contact.all.first.name).to eq('Sojan Jose')
expect(Contact.all.first.additional_attributes['social_telegram_user_id']).to eq(5_171_248)
expect(telegram_channel.inbox.messages.first.content).to eq('Option 1')
end
end
+55 -43
View File
@@ -3156,10 +3156,10 @@
"@braid/vue-formulate-i18n" "^1.16.0"
is-plain-object "^3.0.1"
"@chatwoot/prosemirror-schema@1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@chatwoot/prosemirror-schema/-/prosemirror-schema-1.0.5.tgz#d6053692beae59d466ac0b04128fa157f59eb176"
integrity sha512-dOzkZ2K53PPbE9AQB0RHlVs+GIEyHHdXeeW44dNSEuULwH99PmTpzA2r45QX3uaVa2j7Mip76AQbJZGKbM2fxg==
"@chatwoot/prosemirror-schema@1.0.7":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@chatwoot/prosemirror-schema/-/prosemirror-schema-1.0.7.tgz#5f681773330f8f0fa57a736d3e321b6a11f98124"
integrity sha512-eIgBCeMjV0NEcH9NLbfuDHlLn8DQaCaJPuYlejRFJPQVxZEj6/KT9azwi1WPjOgoIZYrRc8ylaQGVVCX6hj5WQ==
dependencies:
markdown-it-sup "^1.0.0"
prosemirror-commands "^1.1.4"
@@ -3854,21 +3854,22 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
"@june-so/analytics-next@^1.36.5":
version "1.36.5"
resolved "https://registry.yarnpkg.com/@june-so/analytics-next/-/analytics-next-1.36.5.tgz#030ba5a7f8fa232597185cdb706e40b7036ca7f8"
integrity sha512-r2sp4VDngeX/ItQrlgmdKpmghg3OmPvhnTOKJ1huaHbFM2uPELpfnMRRngUVzbgbMoMeTf8QtgQVMjnUsl7G1A==
"@june-so/analytics-next@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@june-so/analytics-next/-/analytics-next-2.0.0.tgz#979035ce812e768370d409c90b541ef238a3daa8"
integrity sha512-7uFP94JLD7mP4qLyOwn5HBs+CC8VlevOkiGd1CIYqPSjSRmbCOI+MVcJNlTAcpyNvMi9iUnWZ3jGVO5177Di4A==
dependencies:
"@lukeed/uuid" "^2.0.0"
"@segment/analytics-core" "1.2.2"
"@segment/analytics.js-video-plugins" "^0.2.1"
"@segment/facade" "3.4.7"
"@segment/tsub" "^0.1.9"
dset "^3.0.0"
encoding "^0.1.13"
js-cookie "^2.2.1"
node-fetch "^2.6.1"
"@segment/facade" "^3.4.9"
"@segment/tsub" "1.0.1"
dset "^3.1.2"
js-cookie "3.0.1"
node-fetch "^2.6.7"
spark-md5 "^3.0.1"
tslib "^2.1.0"
tslib "^2.4.1"
typescript "^4.9.5"
unfetch "^4.1.0"
"@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0":
@@ -4068,6 +4069,15 @@
resolved "https://registry.yarnpkg.com/@scmmishra/pico-search/-/pico-search-0.5.1.tgz#fe10a5615259623bff0095ad9af26e15d84379cb"
integrity sha512-OKyoGy9f0Y47khEpgP3eLw2aKujJe/ndAE0IbovpsVh210GCmECtl8DLYKL+3abcorX3+e6K8S3eFs2HdH8Ksw==
"@segment/analytics-core@1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@segment/analytics-core/-/analytics-core-1.2.2.tgz#46b1465181e089bd75890d37c68c58de36fa6c44"
integrity sha512-zVWSDcyh7Rp32xL5v2fuEk2yZxxy+JA93vF1L3EF9XAYLSra/uEHJEswOWieXSdDHVRHes7APORp136usFE/tw==
dependencies:
"@lukeed/uuid" "^2.0.0"
dset "^3.1.2"
tslib "^2.4.1"
"@segment/analytics.js-video-plugins@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@segment/analytics.js-video-plugins/-/analytics.js-video-plugins-0.2.1.tgz#3596fa3887dcd9df5978dc566edf4a0aea2a9b1e"
@@ -4075,14 +4085,13 @@
dependencies:
unfetch "^3.1.1"
"@segment/facade@3.4.7":
version "3.4.7"
resolved "https://registry.yarnpkg.com/@segment/facade/-/facade-3.4.7.tgz#27e469189d45d5a2806d16571722e6b00d81429a"
integrity sha512-Tj4aJsdmR9cl7xm7BT0NF9kYOnbIJ/3DdC1yOiLnjQRbHcnOq7WWkMDug/JCSEPF2loxGhG/oTeZybR3hpG3MA==
"@segment/facade@^3.4.9":
version "3.4.10"
resolved "https://registry.yarnpkg.com/@segment/facade/-/facade-3.4.10.tgz#118fab29cf2250d3128f9b2a16d6ec76f86e3710"
integrity sha512-xVQBbB/lNvk/u8+ey0kC/+g8pT3l0gCT8O2y9Z+StMMn3KAFAQ9w8xfgef67tJybktOKKU7pQGRPolRM1i1pdA==
dependencies:
"@segment/isodate-traverse" "^1.1.1"
inherits "^2.0.4"
klona "^2.0.5"
new-date "^1.0.3"
obj-case "0.2.1"
@@ -4098,10 +4107,10 @@
resolved "https://registry.yarnpkg.com/@segment/isodate/-/isodate-1.0.3.tgz#f44e8202d5edd277ce822785239474b2c9411d4a"
integrity sha512-BtanDuvJqnACFkeeYje7pWULVv8RgZaqKHWwGFnL/g/TH/CcZjkIVTfGDp/MAxmilYHUkrX70SqwnYSTNEaN7A==
"@segment/tsub@^0.1.9":
version "0.1.12"
resolved "https://registry.yarnpkg.com/@segment/tsub/-/tsub-0.1.12.tgz#1c301a8b81e5dbda54eb0435ae808fbe65553f23"
integrity sha512-35JB0+HuMZrn7mus/s4yOHAcuid+MzaOYxV8YAogTR4Z7AsHwn/Zn/y9XdoHp2kKdn54s6jO4IaO826v0j7qmw==
"@segment/tsub@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@segment/tsub/-/tsub-1.0.1.tgz#4d7fad9eb84b354eec559020da7408fac3046b33"
integrity sha512-rUpvlj/rRfOolk5rjwyrsbl0qzGLsaYgFNEiOSrwrWDryDPq1ZGdo+3Eb+E8+EC0yZOAO4F1DjJfLtaSifpx7w==
dependencies:
"@stdlib/math-base-special-ldexp" "^0.0.5"
dlv "^1.1.3"
@@ -10205,11 +10214,16 @@ dotenv@^8.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
dset@^3.0.0, dset@^3.1.1:
dset@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.2.tgz#89c436ca6450398396dc6538ea00abc0c54cd45a"
integrity sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==
dset@^3.1.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.3.tgz#c194147f159841148e8e34ca41f638556d9542d2"
integrity sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==
duplexify@^3.4.2, duplexify@^3.6.0:
version "3.7.1"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
@@ -10311,13 +10325,6 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
encoding@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@@ -12392,7 +12399,7 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@0.6.3, iconv-lite@^0.6.2:
iconv-lite@0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
@@ -13787,10 +13794,15 @@ js-beautify@^1.6.12:
mkdirp "^1.0.4"
nopt "^5.0.0"
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
js-cookie@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
js-cookie@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
js-string-escape@^1.0.1:
version "1.0.1"
@@ -14046,11 +14058,6 @@ klona@^2.0.4:
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
klona@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
language-subtag-registry@~0.3.2:
version "0.3.21"
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"
@@ -19807,7 +19814,7 @@ tslib@^2.1.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
tslib@^2.5.0, tslib@^2.6.0:
tslib@^2.4.1, tslib@^2.5.0, tslib@^2.6.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
@@ -19937,6 +19944,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^4.9.5:
version "4.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
typeson-registry@^1.0.0-alpha.20:
version "1.0.0-alpha.39"
resolved "https://registry.yarnpkg.com/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz#9e0f5aabd5eebfcffd65a796487541196f4b1211"