Commit 0b973e61 authored by DatHV's avatar DatHV
Browse files

update build web, ui

parent b7cceccb
#!/bin/bash
# Script build Production (tối đa hoá nén/ẩn mã) để gửi cho T3 tích hợp host app
set -e
echo "🚀 Building Production package (max optimization)..."
# 1) Chuyển env sang PRODUCTION
./scripts/switch_env.sh prod
# 2) Clean & get deps (đảm bảo build fresh)
echo "🧹 Cleaning..."
flutter clean || true
rm -rf .dart_tool build || true
echo "📦 Getting packages..."
flutter pub get
# 3) Build web (ẩn mã JS tối đa, bỏ source maps, tắt service worker để tránh cache khi cần)
# - --release: bật dart2js minify + tree-shake
# - --no-source-maps: không phát hành sourcemaps (ẩn mã tối đa)
# - --pwa-strategy=none: tắt SW nếu không cần PWA để tránh cache khó debug khi embed
# - --dart-define=FLUTTER_WEB_USE_SKIA=false: ưu tiên HTML renderer để hạn chế sự cố CORS ảnh khi host
echo "🔨 Flutter build web (release, no source maps)..."
flutter build web \
--release \
--no-source-maps \
--pwa-strategy=none \
--dart-define=FLUTTER_WEB_USE_SKIA=false
echo "✅ Build completed. Preparing artifact..."
# 4) Đóng gói artifact gọn để gửi cho T3
STAMP=$(date +%Y%m%d_%H%M%S)
OUT_DIR="web_prod_${STAMP}"
mkdir -p "$OUT_DIR"
cp -r build/web/* "$OUT_DIR/"
# Tuỳ chọn: loại bỏ file không cần thiết (nếu có)
find "$OUT_DIR" -name "*.map" -type f -delete || true
ZIP_FILE="${OUT_DIR}.zip"
echo "📦 Creating ${ZIP_FILE} ..."
zip -rq "$ZIP_FILE" "$OUT_DIR"
echo ""
echo "🎉 Production artifact is ready"
echo " Folder: $OUT_DIR"
echo " Zip: $ZIP_FILE"
echo ""
echo "📌 Gợi ý gửi cho T3: chỉ gửi file .zip hoặc nội dung thư mục $OUT_DIR"
echo " - Đã tối đa hoá ẩn mã (minified), không đính kèm source maps"
echo " - Không bật service worker để tránh cache khi embed trong host app"
echo " - Ưu tiên HTML renderer để giảm sự cố CORS ảnh"
#!/bin/bash
# Script to copy icons for different flavors
# This script copies the appropriate icon files to the Android res directories
echo "Copying icons for different flavors..."
# Function to copy icons for a specific flavor
copy_icons_for_flavor() {
local flavor=$1
local source_dir="assets/icons/$flavor"
local target_dir="android/app/src/$flavor/res"
echo "Copying icons for flavor: $flavor"
# Create target directory if it doesn't exist
mkdir -p "$target_dir/mipmap-hdpi"
mkdir -p "$target_dir/mipmap-mdpi"
mkdir -p "$target_dir/mipmap-xhdpi"
mkdir -p "$target_dir/mipmap-xxhdpi"
mkdir -p "$target_dir/mipmap-xxxhdpi"
mkdir -p "$target_dir/drawable-hdpi"
mkdir -p "$target_dir/drawable-mdpi"
mkdir -p "$target_dir/drawable-xhdpi"
mkdir -p "$target_dir/drawable-xxhdpi"
mkdir -p "$target_dir/drawable-xxxhdpi"
mkdir -p "$target_dir/values"
# Copy app icon (you'll need to create different sizes)
if [ -f "$source_dir/app_icon.png" ]; then
cp "$source_dir/app_icon.png" "$target_dir/mipmap-hdpi/ic_launcher.png"
cp "$source_dir/app_icon.png" "$target_dir/mipmap-mdpi/ic_launcher.png"
cp "$source_dir/app_icon.png" "$target_dir/mipmap-xhdpi/ic_launcher.png"
cp "$source_dir/app_icon.png" "$target_dir/mipmap-xxhdpi/ic_launcher.png"
cp "$source_dir/app_icon.png" "$target_dir/mipmap-xxxhdpi/ic_launcher.png"
fi
# Copy foreground icon
if [ -f "$source_dir/ic_foreground.png" ]; then
cp "$source_dir/ic_foreground.png" "$target_dir/drawable-hdpi/ic_launcher_foreground.png"
cp "$source_dir/ic_foreground.png" "$target_dir/drawable-mdpi/ic_launcher_foreground.png"
cp "$source_dir/ic_foreground.png" "$target_dir/drawable-xhdpi/ic_launcher_foreground.png"
cp "$source_dir/ic_foreground.png" "$target_dir/drawable-xxhdpi/ic_launcher_foreground.png"
cp "$source_dir/ic_foreground.png" "$target_dir/drawable-xxxhdpi/ic_launcher_foreground.png"
fi
# Create colors.xml for this flavor
case $flavor in
"dev")
echo '<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FEE440</color>
</resources>' > "$target_dir/values/colors.xml"
;;
"stg")
echo '<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#E0E0E0</color>
</resources>' > "$target_dir/values/colors.xml"
;;
"pro")
echo '<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>' > "$target_dir/values/colors.xml"
;;
esac
echo "Icons copied for $flavor"
}
# Copy icons for each flavor
copy_icons_for_flavor "dev"
copy_icons_for_flavor "stg"
copy_icons_for_flavor "pro"
echo "Icon copying completed!"
#!/bin/bash
# Complete script to run Flutter web app with CORS solution
echo "🌐 Starting Flutter web app with CORS solution..."
# Check if we're in the right directory
if [ ! -f "pubspec.yaml" ]; then
echo "❌ Please run this script from the Flutter project root directory"
exit 1
fi
# Build Flutter web app (force HTML renderer via dart-define for older Flutter)
echo "🔨 Building Flutter web app (HTML renderer)..."
flutter build web --release --dart-define=FLUTTER_WEB_USE_SKIA=false
if [ $? -eq 0 ]; then
echo "✅ Build successful!"
else
echo "❌ Build failed!"
exit 1
fi
# Start web server in background
echo "🚀 Starting web server..."
cd build/web
python3 -c "
import http.server
import socketserver
import socket
class CORSHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin')
super().end_headers()
def do_OPTIONS(self):
self.send_response(200)
self.end_headers()
def log_message(self, format, *args):
print(f'🌐 {format % args}')
def find_free_port(start_port=8080, max_attempts=10):
for port in range(start_port, start_port + max_attempts):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('', port))
return port
except OSError:
continue
return None
PORT = find_free_port(8080, 20)
if not PORT:
print('❌ No free port found')
exit(1)
print(f'🚀 Server running at http://localhost:{PORT}')
with socketserver.TCPServer(('', PORT), CORSHTTPRequestHandler) as httpd:
httpd.serve_forever()
" &
SERVER_PID=$!
# Wait for server to start
sleep 3
# Open browser with CORS disabled
echo "🌐 Opening browser with CORS disabled..."
if command -v open &> /dev/null; then
# macOS
open -n -a "Google Chrome" --args --disable-web-security --user-data-dir=/tmp/chrome_dev --disable-features=VizDisplayCompositor http://localhost:8080
elif command -v google-chrome &> /dev/null; then
# Linux
google-chrome --disable-web-security --user-data-dir=/tmp/chrome_dev --disable-features=VizDisplayCompositor http://localhost:8080 &
else
echo "⚠️ Chrome not found. Please open manually: http://localhost:8080"
fi
echo ""
echo "✅ Setup complete!"
echo "🌐 Web app: http://localhost:8080"
echo "🔧 CORS disabled in browser for development"
echo "📋 Retry limit: 3 attempts for Super App data"
echo ""
echo "Press Ctrl+C to stop the server"
# Wait for user to stop
wait $SERVER_PID
#!/bin/bash
# Script để chuyển đổi giữa môi trường dev và production
ENV=${1:-"prod"}
if [ "$ENV" = "dev" ]; then
echo "🔧 Switching to DEVELOPMENT environment..."
cp assets/config/env_dev.json assets/config/env.json
echo "✅ Switched to DEVELOPMENT:"
echo " - API: https://api.sandbox.mypoint.com.vn/8854/gup2start/rest"
echo " - Logging: Enabled"
echo " - Flavor: dev"
elif [ "$ENV" = "prod" ]; then
echo "🚀 Switching to PRODUCTION environment..."
# Create production config directly
cat > assets/config/env.json << 'EOF'
{
"flavor":"pro",
"baseUrl":"https://api.mypoint.com.vn/8854/gup2start/rest",
"t3Token":"runner-env-flavor-pro",
"enableLogging":false
}
EOF
echo "✅ Switched to PRODUCTION:"
echo " - API: https://api.mypoint.com.vn/8854/gup2start/rest"
echo " - Logging: Disabled"
echo " - Flavor: pro"
else
echo "❌ Invalid environment. Use 'dev' or 'prod'"
echo "Usage: ./scripts/switch_env.sh [dev|prod]"
exit 1
fi
echo ""
echo "📋 Current config:"
cat assets/config/env.json
echo ""
echo "💡 Now you can run:"
echo " - Development: ./scripts/run_web_complete.sh"
echo " - Export: ./export_web.sh"
#!/bin/bash
# Script to test x-app-sdk integration locally
echo "🧪 Testing x-app-sdk integration..."
# Check if we're in the right directory
if [ ! -f "pubspec.yaml" ]; then
echo "❌ Please run this script from the Flutter project root directory"
exit 1
fi
# Install web dependencies
echo "📦 Installing web dependencies..."
cd web
if [ -f "package.json" ]; then
npm install
if [ $? -eq 0 ]; then
echo "✅ Web dependencies installed successfully!"
else
echo "❌ Failed to install web dependencies"
exit 1
fi
else
echo "❌ No package.json found in web directory"
exit 1
fi
cd ..
# Build Flutter web app
echo "🔨 Building Flutter web app..."
flutter build web --release
if [ $? -eq 0 ]; then
echo "✅ Build successful!"
# Start local server with CORS headers
echo "🚀 Starting local server with CORS support on port 8080..."
echo "🌐 Open your browser and go to: http://localhost:8080"
echo ""
echo "🔧 To test x-app-sdk integration:"
echo "1. Open browser console (F12)"
echo "2. Check for x-app-sdk initialization logs"
echo "3. Look for 'AppHostData' object in window"
echo "4. Test with URL parameters: ?token=test123&user={\"id\":\"user123\"}"
echo ""
echo "⚠️ Note: If you see CORS errors, use the CORS proxy script:"
echo " ./scripts/run_web_with_cors.sh"
echo ""
echo "Press Ctrl+C to stop the server"
# Start server with CORS headers
cd build/web
python3 -c "
import http.server
import socketserver
from urllib.parse import urlparse
class CORSHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With')
super().end_headers()
def do_OPTIONS(self):
self.send_response(200)
self.end_headers()
PORT = 8080
with socketserver.TCPServer(('', PORT), CORSHTTPRequestHandler) as httpd:
print(f'Serving at port {PORT}')
httpd.serve_forever()
"
else
echo "❌ Build failed!"
exit 1
fi
// Firebase Messaging Service Worker (stub for web)
// We disable messaging on web, this file avoids 404 registration errors.
self.addEventListener('install', (event) => {
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim());
});
// No push/message handlers; messaging is disabled on web.
...@@ -31,6 +31,249 @@ ...@@ -31,6 +31,249 @@
<title>mypoint_flutter_app</title> <title>mypoint_flutter_app</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<!-- x-app-sdk integration for mini app -->
<script>
// Global object to store app host data
window.AppHostData = {
token: null,
user: null,
isReady: false,
error: null
};
// Function to initialize x-app-sdk and get data from Super App
async function initializeXAppSDK() {
try {
console.log('🔍 Initializing x-app-sdk in mini app...');
// Check if x-app-sdk is available from Super App
if (typeof window.getToken === 'undefined' || typeof window.getInfo === 'undefined') {
console.warn('⚠️ x-app-sdk not available from Super App, trying fallback...');
await tryFallbackMethods();
return;
}
// Get token from Super App
try {
const token = await window.getToken();
if (token) {
window.AppHostData.token = token;
console.log('✅ Token from Super App:', token.substring(0, 8) + '...');
}
} catch (tokenError) {
console.warn('⚠️ Could not get token from Super App:', tokenError);
}
// Get user info from Super App
try {
// Try to get user ID first
const userInfo = await window.getInfo('USER_ID');
if (userInfo && userInfo.data) {
const userData = {
id: userInfo.data
};
// Try to get additional user details
try {
const userName = await window.getInfo('USER_NAME');
if (userName && userName.data) {
userData.name = userName.data;
}
} catch (e) {
console.log('User name not available');
}
try {
const userEmail = await window.getInfo('USER_EMAIL');
if (userEmail && userEmail.data) {
userData.email = userEmail.data;
}
} catch (e) {
console.log('User email not available');
}
try {
const userPhone = await window.getInfo('USER_PHONE');
if (userPhone && userPhone.data) {
userData.phone = userPhone.data;
}
} catch (e) {
console.log('User phone not available');
}
window.AppHostData.user = userData;
console.log('✅ User info from Super App:', userData);
}
} catch (userError) {
console.warn('⚠️ Could not get user info from Super App:', userError);
}
// Mark as ready if we got at least token or user info
if (window.AppHostData.token || window.AppHostData.user) {
window.AppHostData.isReady = true;
console.log('✅ x-app-sdk initialized successfully in mini app');
} else {
console.log('❌ No data available from Super App');
window.AppHostData.error = 'No data available from Super App';
}
// Notify Flutter app that data is ready
if (window.flutter_inappwebview) {
window.flutter_inappwebview.callHandler('onAppHostDataReady', window.AppHostData);
}
} catch (error) {
console.error('❌ Error initializing x-app-sdk in mini app:', error);
window.AppHostData.error = error.message;
}
}
// Fallback methods if x-app-sdk is not available
async function tryFallbackMethods() {
try {
console.log('🔍 Trying fallback methods...');
// Try URL parameters
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('token');
const userStr = urlParams.get('user');
if (token) {
window.AppHostData.token = token;
if (userStr) {
try {
window.AppHostData.user = JSON.parse(userStr);
} catch (e) {
console.warn('Failed to parse user from URL');
}
}
window.AppHostData.isReady = true;
console.log('✅ Data loaded from URL parameters (fallback)');
return;
}
// Try localStorage
const storedToken = localStorage.getItem('app_host_token');
const storedUser = localStorage.getItem('app_host_user');
if (storedToken) {
window.AppHostData.token = storedToken;
if (storedUser) {
try {
window.AppHostData.user = JSON.parse(storedUser);
} catch (e) {
console.warn('Failed to parse user from localStorage');
}
}
window.AppHostData.isReady = true;
console.log('✅ Data loaded from localStorage (fallback)');
} else {
console.log('❌ No fallback data available');
window.AppHostData.error = 'No data available from Super App or fallback methods';
}
} catch (error) {
console.error('❌ Error in fallback methods:', error);
window.AppHostData.error = error.message;
}
}
// Function to close app and return to Super App
function closeApp(data) {
try {
console.log('🚪 Closing app and returning to Super App...', data ? 'with data:' : 'without data');
if (data) {
console.log('📤 Data to return:', data);
}
// Check if closeApp is available from Super App
if (typeof window.closeApp === 'function') {
// Call Super App's closeApp function
window.closeApp(data);
console.log('✅ closeApp called successfully');
} else {
console.warn('⚠️ closeApp not available from Super App, using fallback');
// Fallback: try to close the window or navigate back
if (window.history.length > 1) {
window.history.back();
} else {
window.close();
}
}
} catch (error) {
console.error('❌ Error closing app:', error);
// Fallback on error
try {
if (window.history.length > 1) {
window.history.back();
} else {
window.close();
}
} catch (fallbackError) {
console.error('❌ Fallback close also failed:', fallbackError);
}
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', initializeXAppSDK);
// Also try to initialize immediately in case DOM is already ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeXAppSDK);
} else {
initializeXAppSDK();
}
</script>
<!-- Auto-rewrite external image URLs to same-origin proxy (/ext-img) to avoid CORS in production -->
<script>
(function () {
function toProxyUrl(url) {
try {
var u = new URL(url, window.location.href);
if (u.origin === window.location.origin) return url; // already same-origin
var pathAndQuery = u.pathname + (u.search || "");
var params = new URLSearchParams({ scheme: u.protocol.replace(':',''), host: u.host, path: pathAndQuery });
return '/ext-img?' + params.toString();
} catch (e) {
return url;
}
}
function rewriteImg(el) {
if (!el || !el.getAttribute) return;
var src = el.getAttribute('src');
if (!src) return;
if (/^https?:\/\//i.test(src)) {
var newSrc = toProxyUrl(src);
if (newSrc !== src) el.setAttribute('src', newSrc);
}
}
// Initial pass for any existing <img>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('img[src]').forEach(rewriteImg);
});
// Observe future <img> insertions/changes (Flutter HTML renderer uses <img>)
var mo = new MutationObserver(function (mutations) {
mutations.forEach(function (m) {
if (m.type === 'attributes' && m.attributeName === 'src' && m.target.tagName === 'IMG') {
rewriteImg(m.target);
}
if (m.type === 'childList') {
m.addedNodes.forEach(function (n) {
if (n && n.tagName === 'IMG') rewriteImg(n);
if (n && n.querySelectorAll) n.querySelectorAll('img[src]').forEach(rewriteImg);
});
}
});
});
try { mo.observe(document.documentElement, { subtree: true, childList: true, attributes: true, attributeFilter: ['src'] }); } catch (e) {}
})();
</script>
</head> </head>
<body> <body>
<script src="flutter_bootstrap.js" async></script> <script src="flutter_bootstrap.js" async></script>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment