Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b0300963e | ||
|
|
b2a8e03f62 | ||
|
|
8c0cac0532 | ||
|
|
1fd280ec0e | ||
|
|
18b6681e9b | ||
|
|
8558b6f4c0 | ||
|
|
90d7735ade | ||
|
|
d916557fda | ||
|
|
c8a9e36085 | ||
|
|
43d950001c | ||
|
|
f701a4df9d | ||
|
|
f5b2e7b5de | ||
|
|
cf297801fd | ||
|
|
5c350348d6 |
+1
-1
@@ -1 +1 @@
|
||||
4.15.1
|
||||
4.14.2
|
||||
|
||||
@@ -145,12 +145,6 @@
|
||||
--black-alpha-2: 0, 0, 0, 0.04;
|
||||
--border-blue: 39, 129, 246, 0.5;
|
||||
--white-alpha: 255, 255, 255, 0.8;
|
||||
|
||||
// Voice call widget - light mode
|
||||
--call-widget: 33, 34, 38, 0.95;
|
||||
--call-widget-border: 255, 255, 255, 0.1;
|
||||
--call-widget-text: 237, 238, 240, 1;
|
||||
--call-widget-sub-text: 173, 177, 184, 1;
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -297,12 +291,6 @@
|
||||
--border-blue: 39, 129, 246, 0.5;
|
||||
--border-container: 255, 255, 255, 0;
|
||||
--white-alpha: 255, 255, 255, 0.1;
|
||||
|
||||
// Voice call widget - dark mode
|
||||
--call-widget: 50, 53, 61, 1;
|
||||
--call-widget-border: 255, 255, 255, 0.07;
|
||||
--call-widget-text: 237, 238, 240, 1;
|
||||
--call-widget-sub-text: 173, 177, 184, 1;
|
||||
}
|
||||
}
|
||||
// NEXT COLORS END
|
||||
|
||||
@@ -75,11 +75,11 @@ const channelIcon = computed(() => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col gap-1 pt-4 bg-n-call-widget rounded-2xl shadow-xl outline outline-1 outline-n-call-widget-border backdrop-blur-md"
|
||||
class="flex flex-col gap-2 pt-4 bg-n-solid-2/95 rounded-2xl shadow-xl outline outline-1 outline-n-strong backdrop-blur-md"
|
||||
:class="call?.conversationId ? 'pb-2' : 'pb-4'"
|
||||
>
|
||||
<!-- Top section: status badge + location/inbox + duration -->
|
||||
<div class="flex flex-col gap-3 pb-3 border-b border-n-call-widget-border">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex items-center gap-2 px-4">
|
||||
<!-- Ongoing: status badge on left -->
|
||||
<div v-if="isOngoing" class="flex items-center gap-1.5 shrink-0">
|
||||
@@ -100,10 +100,10 @@ const channelIcon = computed(() => {
|
||||
<Icon
|
||||
v-else-if="!isOngoing"
|
||||
:icon="channelIcon"
|
||||
class="size-3.5 text-n-call-widget-sub-text shrink-0"
|
||||
class="size-3.5 text-n-slate-10 shrink-0"
|
||||
/>
|
||||
<span
|
||||
class="text-xs font-medium text-n-call-widget-sub-text tracking-tight truncate"
|
||||
class="text-xs font-medium text-n-slate-11 tracking-tight truncate"
|
||||
>
|
||||
{{ callInfo.location }}
|
||||
</span>
|
||||
@@ -112,7 +112,7 @@ const channelIcon = computed(() => {
|
||||
<!-- Ongoing: duration on right -->
|
||||
<p
|
||||
v-if="isOngoing"
|
||||
class="font-display text-base font-medium text-n-call-widget-sub-text shrink-0 mb-0 tabular-nums tracking-tight"
|
||||
class="font-display text-base font-medium text-n-slate-11 shrink-0 mb-0 tabular-nums tracking-tight"
|
||||
>
|
||||
{{ duration }}
|
||||
</p>
|
||||
@@ -132,7 +132,7 @@ const channelIcon = computed(() => {
|
||||
slate
|
||||
ghost
|
||||
xs
|
||||
class="!rounded-full -my-1 -me-1 !text-n-call-widget-sub-text"
|
||||
class="!rounded-full -my-1"
|
||||
@click="$emit('dismiss')"
|
||||
/>
|
||||
</div>
|
||||
@@ -149,13 +149,13 @@ const channelIcon = computed(() => {
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p
|
||||
class="font-display text-sm font-medium text-n-call-widget-text truncate mb-0.5 tracking-tight leading-tight"
|
||||
class="font-display text-sm font-medium text-n-slate-12 truncate mb-0.5 tracking-tight leading-tight"
|
||||
>
|
||||
{{ callInfo.contactName }}
|
||||
</p>
|
||||
<p
|
||||
v-if="callInfo.phoneNumber"
|
||||
class="text-sm text-n-call-widget-sub-text truncate mb-0 tracking-tight leading-tight"
|
||||
class="text-sm text-n-slate-11 truncate mb-0 tracking-tight leading-tight"
|
||||
>
|
||||
{{ callInfo.phoneNumber }}
|
||||
</p>
|
||||
@@ -217,23 +217,23 @@ const channelIcon = computed(() => {
|
||||
>
|
||||
<template #icon>
|
||||
<span
|
||||
class="flex items-center gap-1 text-n-call-widget-sub-text group-hover:text-n-call-widget-text"
|
||||
class="flex items-center gap-1 text-n-slate-11 group-hover:text-n-slate-12"
|
||||
>
|
||||
<Icon
|
||||
icon="i-ph-chat-circle-text-bold"
|
||||
class="size-3.5 text-n-call-widget-sub-text shrink-0"
|
||||
class="size-3.5 text-n-slate-11 shrink-0"
|
||||
/>
|
||||
<span class="text-sm tracking-tight tabular-nums">
|
||||
#{{ call.conversationId }}
|
||||
</span>
|
||||
<Icon
|
||||
icon="i-ph-caret-right-bold"
|
||||
class="size-3 text-n-call-widget-sub-text shrink-0"
|
||||
class="size-3 text-n-slate-11 shrink-0"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<span
|
||||
class="text-sm text-n-call-widget-sub-text tracking-tight group-hover:text-n-call-widget-text"
|
||||
class="text-sm text-n-slate-11 tracking-tight group-hover:text-n-slate-12"
|
||||
>
|
||||
{{ $t('CONVERSATION.VOICE_WIDGET.GO_TO_CONVERSATION') }}
|
||||
</span>
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
class UserSessionTrackingService
|
||||
# CFNetwork UAs cannot distinguish iPhone from iPad; both get labelled iPhone here.
|
||||
LEGACY_MOBILE_UAS = [
|
||||
{ match: %r{\Aokhttp/}, platform: 'Android', device: 'Android' },
|
||||
{ match: %r{\AChatwoot/.*CFNetwork.*Darwin}, platform: 'iPhone', device: 'iPhone' }
|
||||
].freeze
|
||||
private_constant :LEGACY_MOBILE_UAS
|
||||
|
||||
def initialize(user:, request:, client_id:)
|
||||
@user = user
|
||||
@request = request
|
||||
@@ -33,7 +26,7 @@ class UserSessionTrackingService
|
||||
def session_attributes
|
||||
browser = Browser.new(@request.user_agent)
|
||||
|
||||
attrs = {
|
||||
{
|
||||
ip_address: @request.remote_ip,
|
||||
user_agent: @request.user_agent,
|
||||
browser_name: browser.name,
|
||||
@@ -42,22 +35,5 @@ class UserSessionTrackingService
|
||||
platform_name: browser.platform.name,
|
||||
platform_version: browser.platform.version
|
||||
}
|
||||
|
||||
patch_for_legacy_mobile(attrs)
|
||||
end
|
||||
|
||||
def patch_for_legacy_mobile(attrs)
|
||||
return attrs unless attrs[:browser_name] == 'Unknown Browser'
|
||||
|
||||
hit = LEGACY_MOBILE_UAS.find { |m| @request.user_agent.to_s.match?(m[:match]) }
|
||||
return attrs unless hit
|
||||
|
||||
attrs.merge(
|
||||
browser_name: 'Chatwoot Mobile',
|
||||
browser_version: nil,
|
||||
platform_name: hit[:platform],
|
||||
platform_version: nil,
|
||||
device_name: hit[:device]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
shared: &shared
|
||||
version: '4.15.1'
|
||||
version: '4.14.2'
|
||||
|
||||
development:
|
||||
<<: *shared
|
||||
|
||||
@@ -200,6 +200,7 @@
|
||||
display_name: Advanced Search
|
||||
enabled: false
|
||||
premium: true
|
||||
chatwoot_internal: true
|
||||
- name: saml
|
||||
display_name: SAML
|
||||
enabled: false
|
||||
|
||||
@@ -75,14 +75,6 @@ module Enterprise::Account::PlanUsageAndLimits # rubocop:disable Metrics/ModuleL
|
||||
end
|
||||
|
||||
def plan_email_limit
|
||||
base_limit = plan_base_email_limit
|
||||
return nil if base_limit.nil?
|
||||
return base_limit if free_plan?
|
||||
|
||||
base_limit * [agent_limits.to_i, 1].max
|
||||
end
|
||||
|
||||
def plan_base_email_limit
|
||||
config = InstallationConfig.find_by(name: 'ACCOUNT_EMAILS_PLAN_LIMITS')&.value
|
||||
return nil if config.blank? || plan_name.blank?
|
||||
|
||||
@@ -92,11 +84,6 @@ module Enterprise::Account::PlanUsageAndLimits # rubocop:disable Metrics/ModuleL
|
||||
nil
|
||||
end
|
||||
|
||||
def free_plan?
|
||||
default_plan = InstallationConfig.find_by(name: 'CHATWOOT_CLOUD_PLANS')&.value&.first
|
||||
default_plan.present? && plan_name&.downcase == default_plan['name']&.downcase
|
||||
end
|
||||
|
||||
def default_captain_limits
|
||||
max_limits = { documents: ChatwootApp.max_limit, responses: ChatwootApp.max_limit }.with_indifferent_access
|
||||
zero_limits = { documents: 0, responses: 0 }.with_indifferent_access
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@chatwoot/chatwoot",
|
||||
"version": "4.15.1",
|
||||
"version": "4.14.2",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"eslint": "eslint app/**/*.{js,vue}",
|
||||
|
||||
@@ -47,75 +47,6 @@ RSpec.describe UserSessionTrackingService do
|
||||
expect(existing.reload.ip_address).to eq('8.8.8.8')
|
||||
expect(existing.last_activity_at).to be_within(1.second).of(Time.current)
|
||||
end
|
||||
|
||||
context 'with a Chatwoot Mobile legacy User-Agent' do
|
||||
let(:request) do
|
||||
instance_double(
|
||||
ActionDispatch::Request,
|
||||
user_agent: ua,
|
||||
remote_ip: '8.8.8.8'
|
||||
)
|
||||
end
|
||||
|
||||
context 'when the UA is okhttp (Android Chatwoot Mobile)' do
|
||||
let(:ua) { 'okhttp/4.9.2' }
|
||||
|
||||
it 'labels the session as Chatwoot Mobile on Android', :aggregate_failures do
|
||||
service.create_or_update!
|
||||
|
||||
session = user.user_sessions.last
|
||||
expect(session.browser_name).to eq('Chatwoot Mobile')
|
||||
expect(session.browser_version).to be_nil
|
||||
expect(session.platform_name).to eq('Android')
|
||||
expect(session.platform_version).to be_nil
|
||||
expect(session.device_name).to eq('Android')
|
||||
expect(session.user_agent).to eq(ua)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the UA is CFNetwork (iOS Chatwoot Mobile)' do
|
||||
let(:ua) { 'Chatwoot/3759 CFNetwork/3886.100.1 Darwin/27.0.0' }
|
||||
|
||||
it 'labels the session as Chatwoot Mobile on iPhone', :aggregate_failures do
|
||||
service.create_or_update!
|
||||
|
||||
session = user.user_sessions.last
|
||||
expect(session.browser_name).to eq('Chatwoot Mobile')
|
||||
expect(session.browser_version).to be_nil
|
||||
expect(session.platform_name).to eq('iPhone')
|
||||
expect(session.platform_version).to be_nil
|
||||
expect(session.device_name).to eq('iPhone')
|
||||
expect(session.user_agent).to eq(ua)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the UA is a real browser (Firefox on Linux)' do
|
||||
let(:ua) { 'Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0' }
|
||||
|
||||
it 'does not override the Browser-derived metadata', :aggregate_failures do
|
||||
service.create_or_update!
|
||||
|
||||
session = user.user_sessions.last
|
||||
expect(session.browser_name).to eq('Firefox')
|
||||
expect(session.platform_name).to eq('Generic Linux')
|
||||
expect(session.device_name).not_to eq('Android')
|
||||
expect(session.device_name).not_to eq('iPhone')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the UA is unknown but does not match any mobile pattern' do
|
||||
let(:ua) { 'curl/8.4.0' }
|
||||
|
||||
it 'leaves the Unknown labels untouched', :aggregate_failures do
|
||||
service.create_or_update!
|
||||
|
||||
session = user.user_sessions.last
|
||||
expect(session.browser_name).to eq('Unknown Browser')
|
||||
expect(session.platform_name).to eq('Unknown')
|
||||
expect(session.device_name).to eq('Unknown')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_activity!' do
|
||||
|
||||
@@ -258,11 +258,6 @@ export const colors = {
|
||||
black2: 'rgba(var(--black-alpha-2))',
|
||||
white: 'rgba(var(--white-alpha))',
|
||||
},
|
||||
// Voice call widget
|
||||
'call-widget': 'rgba(var(--call-widget))',
|
||||
'call-widget-border': 'rgba(var(--call-widget-border))',
|
||||
'call-widget-text': 'rgba(var(--call-widget-text))',
|
||||
'call-widget-sub-text': 'rgba(var(--call-widget-sub-text))',
|
||||
// Border colors
|
||||
weak: 'rgb(var(--border-weak) / <alpha-value>)',
|
||||
container: 'rgba(var(--border-container))',
|
||||
|
||||
Reference in New Issue
Block a user