ts-glitter 20.4.2 → 20.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/seo-config.ts CHANGED
@@ -2,6 +2,15 @@ import db from './modules/database.js';
2
2
  import { Manager } from './api-public/services/manager.js';
3
3
  import { UtDatabase } from './api-public/utils/ut-database.js';
4
4
  import { Shopping } from './api-public/services/shopping.js';
5
+ import { config, ConfigSetting, saasConfig } from './config.js';
6
+ import { ApiPublic } from './api-public/services/public-table-check.js';
7
+ import { App } from './services/app.js';
8
+ import { User } from './api-public/services/user.js';
9
+ import { Monitor } from './api-public/services/monitor.js';
10
+ import { Seo } from './services/seo.js';
11
+ import { Language } from './Language.js';
12
+ import express from 'express';
13
+ import { Private_config } from './services/private_config.js';
5
14
 
6
15
  const html = String.raw;
7
16
 
@@ -383,6 +392,410 @@ export class SeoConfig {
383
392
  })
384
393
  .join('');
385
394
  }
395
+
396
+ // 所有內容
397
+ public static async seoDetail(in_app:string,req: express.Request, resp: express.Response){
398
+ const og_url = req.headers['x-original-url'];
399
+ try {
400
+ if (req.query.state === 'google_login') {
401
+ req.query.page = 'login';
402
+ }
403
+ let appName:any = in_app;
404
+ if (req.query.appName) {
405
+ appName = req.query.appName;
406
+ } else if (og_url) {
407
+ const new_app = (
408
+ await db.query(
409
+ `SELECT *
410
+ FROM \`${saasConfig.SAAS_NAME}\`.app_config
411
+ where LOWER(domain) = ?`,
412
+ [og_url]
413
+ )
414
+ )[0];
415
+ if (new_app && new_app.appName) {
416
+ appName = new_app && new_app.appName;
417
+ } else {
418
+ return {
419
+ head: '',
420
+ body: `<script>window.location.href='https://shopnex.tw'</script>`,
421
+ };
422
+ }
423
+ }
424
+ req.headers['g-app'] = appName;
425
+ const start = new Date().getTime();
426
+ console.log(`getPageInfo==>`, (new Date().getTime() - start) / 1000);
427
+ //要進行initial避免找不到DB
428
+ await ApiPublic.createScheme(appName);
429
+ const find_app = ApiPublic.app301.find(dd => {
430
+ return dd.app_name === appName;
431
+ });
432
+ if (find_app) {
433
+ const router = find_app.router.find(dd => {
434
+ if (dd.legacy_url.startsWith('/')) {
435
+ dd.legacy_url = dd.legacy_url.substring(1);
436
+ }
437
+ if (dd.new_url.startsWith('/')) {
438
+ dd.new_url = dd.new_url.substring(1);
439
+ }
440
+ return dd.legacy_url === req.query.page;
441
+ });
442
+ if (router) {
443
+ return {
444
+ redirect: `https://${(await App.checkBrandAndMemberType(appName)).domain}/${router.new_url}`,
445
+ };
446
+ }
447
+ }
448
+ //SEO內容
449
+ let seo_content: string[] = [];
450
+ let [
451
+ customCode,
452
+ FBCode,
453
+ store_info,
454
+ language_label,
455
+ check_schema,
456
+ brandAndMemberType,
457
+ login_config,
458
+ ip_country,
459
+ ] = await Promise.all([
460
+ new User(appName).getConfigV2({
461
+ key: 'ga4_config',
462
+ user_id: 'manager',
463
+ }),
464
+ new User(appName).getConfigV2({
465
+ key: 'login_fb_setting',
466
+ user_id: 'manager',
467
+ }),
468
+ new User(appName).getConfigV2({
469
+ key: 'store-information',
470
+ user_id: 'manager',
471
+ }),
472
+ new User(appName).getConfigV2({
473
+ key: 'language-label',
474
+ user_id: 'manager',
475
+ }),
476
+ ApiPublic.createScheme(appName),
477
+ App.checkBrandAndMemberType(appName),
478
+ new User(req.get('g-app') as string, req.body.token).getConfigV2({
479
+ key: 'login_config',
480
+ user_id: 'manager',
481
+ }),
482
+ User.ipInfo((req.query.ip || req.headers['x-real-ip'] || req.ip) as string),
483
+ ]);
484
+ //取得多國語言
485
+ const language: any = await SeoConfig.language(store_info, req);
486
+ //插入瀏覽紀錄
487
+ Monitor.insertHistory({
488
+ req_type: 'file',
489
+ req: req,
490
+ });
491
+ console.log(`app_info==>`,{
492
+ page:req.query.page,
493
+ appName:appName
494
+ })
495
+ //取得SEO頁面資訊
496
+ let data = await Seo.getPageInfo(appName, req.query.page as string, language);
497
+ //首頁SEO
498
+ let home_page_data = await (async () => {
499
+ return await Seo.getPageInfo(appName, 'index', language);
500
+ })();
501
+ if(`${req.query.page}`.startsWith('products/') && !data){
502
+ data=home_page_data
503
+ }
504
+ if (data && data.page_config) {
505
+ data.page_config = data.page_config ?? {};
506
+ const d = data.page_config.seo ?? {};
507
+ //商品搜索
508
+ if (`${req.query.page}`.startsWith('products/')) {
509
+ await SeoConfig.productSEO({
510
+ data,
511
+ language,
512
+ appName,
513
+ product_id: req.query.product_id as any,
514
+ page: req.query.page as any,
515
+ });
516
+ } else if (`${req.query.page}` === 'blogs') {
517
+ //網誌目錄
518
+ const seo = await new User(req.get('g-app') as string, req.body.token).getConfigV2({
519
+ key: 'article_seo_data_' + language,
520
+ user_id: 'manager',
521
+ });
522
+ data.page_config.seo.title = seo.title || data.page_config.seo.title;
523
+ data.page_config.seo.content = seo.content || data.page_config.seo.content;
524
+ data.page_config.seo.keywords = seo.keywords || data.page_config.seo.keywords;
525
+ data.page_config.seo.image = seo.image || data.page_config.seo.image;
526
+ data.page_config.seo.logo = seo.logo || data.page_config.seo.logo;
527
+ } else if (`${req.query.page}`.startsWith('blogs/')) {
528
+ //網誌搜索
529
+ data.page_config.seo = await SeoConfig.articleSeo({
530
+ article: req.query.article as any,
531
+ page: req.query.page as any,
532
+ language,
533
+ appName,
534
+ data,
535
+ });
536
+ } else if (`${req.query.page}`.startsWith('pages/')) {
537
+ //頁面搜索
538
+ await SeoConfig.articleSeo({
539
+ article: req.query.article as any,
540
+ page: req.query.page as any,
541
+ language,
542
+ appName,
543
+ data,
544
+ });
545
+ } else if (['privacy', 'term', 'refund', 'delivery'].includes(`${req.query.page}`)) {
546
+ data.page_config.seo = {
547
+ title: Language.text(`${req.query.page}`, language),
548
+ content: Language.text(`${req.query.page}`, language),
549
+ };
550
+ } else if (d.type !== 'custom') {
551
+ data = home_page_data;
552
+ }
553
+ const preload =
554
+ req.query.isIframe === 'true'
555
+ ? {}
556
+ : await App.preloadPageData(appName, req.query.page as any, language);
557
+ data.page_config = data.page_config ?? {};
558
+ data.page_config.seo = data.page_config.seo ?? {};
559
+ const seo_detail = await getSeoDetail(appName, req);
560
+ if (seo_detail) {
561
+ Object.keys(seo_detail).map(dd => {
562
+ data.page_config.seo[dd] = seo_detail[dd];
563
+ });
564
+ }
565
+ let link_prefix = req.originalUrl.split('/')[1];
566
+ if (link_prefix.includes('?')) {
567
+ link_prefix = link_prefix.substring(0, link_prefix.indexOf('?'));
568
+ }
569
+ if (ConfigSetting.is_local) {
570
+ if (link_prefix !== 'shopnex' && link_prefix !== 'codenex_v2') {
571
+ link_prefix = '';
572
+ }
573
+ } else {
574
+ link_prefix = '';
575
+ }
576
+ let distribution_code = '';
577
+ req.query.page = req.query.page || 'index';
578
+ if ((req.query.page as string).split('/')[0] === 'order_detail' && req.query.EndCheckout === '1') {
579
+ distribution_code = `
580
+ localStorage.setItem('distributionCode','');
581
+ `;
582
+ }
583
+ console.log(`req.query.page`,req.query.page)
584
+ //分銷連結頁面SEO
585
+ if (
586
+ (req.query.page as string).split('/')[0] === 'distribution' &&
587
+ (req.query.page as string).split('/')[1]
588
+ ) {
589
+ distribution_code = await SeoConfig.distributionSEO({
590
+ appName: appName,
591
+ url: req.url,
592
+ page: req.query.page as string,
593
+ link_prefix: link_prefix,
594
+ data,
595
+ language,
596
+ });
597
+ }
598
+ //分類頁面SEO
599
+ if (
600
+ (req.query.page as string).split('/')[0] === 'collections' &&
601
+ (req.query.page as string).split('/')[1]
602
+ ) {
603
+ await SeoConfig.collectionSeo({ appName, language, data, page: req.query.page as string });
604
+ }
605
+ //FB像素
606
+ if (FBCode) {
607
+ //IOS系統必須同意後才可追蹤
608
+ if(!((req.headers['user-agent'] as string).includes('iosGlitter') && !((req.headers['user-agent'] as string).includes('allow_track')))){
609
+ seo_content.push(SeoConfig.fbCode(FBCode));
610
+ }
611
+ }
612
+
613
+ const home_seo = home_page_data.page_config.seo || {};
614
+ const seo:any = data.page_config.seo;
615
+ const head = [
616
+ (() => {
617
+ return html`
618
+ ${(() => {
619
+ if (req.query.type === 'editor') {
620
+ return SeoConfig.editorSeo;
621
+ } else {
622
+ const d=seo
623
+ return html`<title>
624
+ ${[home_seo.title_prefix || '', d.title || '', home_seo.title_suffix || ''].join('') ||
625
+ '尚未設定標題'}
626
+ </title>
627
+ <link
628
+ rel="canonical"
629
+ href="${(() => {
630
+ if (data.tag === 'index') {
631
+ return `https://${brandAndMemberType.domain}`;
632
+ } else if (req.query.page === 'blogs') {
633
+ return `https://${brandAndMemberType.domain}/blogs`;
634
+ } {
635
+ return `https://${brandAndMemberType.domain}/${data.tag}`;
636
+ }
637
+ })()}"
638
+ />
639
+ ${((data.tag !== req.query.page || req.query.page === 'index-mobile') && req.query.page !== 'blogs')
640
+ ? `<meta name="robots" content="noindex">`
641
+ : `<meta name="robots" content="index, follow"/>`}
642
+ <meta name="keywords" content="${(d.keywords || '尚未設定關鍵字').replace(/"/g, '&quot;')}" />
643
+ <link
644
+ id="appImage"
645
+ rel="shortcut icon"
646
+ href="${d.logo || home_seo.logo || ''}"
647
+ type="image/x-icon"
648
+ />
649
+ <link rel="icon" href="${d.logo || home_seo.logo || ''}" type="image/png" sizes="128x128" />
650
+ <meta property="og:image" content="${d.image || home_seo.image || ''}" />
651
+ <meta
652
+ property="og:title"
653
+ content="${(d.title ?? '').replace(/\n/g, '').replace(/"/g, '&quot;')}"
654
+ />
655
+ <meta
656
+ name="description"
657
+ content="${(d.content ?? '').replace(/\n/g, '').replace(/"/g, '&quot;')}"
658
+ />
659
+ <meta
660
+ name="og:description"
661
+ content="${(d.content ?? '').replace(/\n/g, '').replace(/"/g, '&quot;')}"
662
+ />
663
+
664
+ ${[{ src: 'css/front-end.css', type: 'text/css' }]
665
+ .map(dd => {
666
+ return html`
667
+ <link href="/${link_prefix && `${link_prefix}/`}${dd.src}" type="${dd.type}"
668
+ rel="stylesheet"></link>`;
669
+ })
670
+ .join('')} `;
671
+ }
672
+ })()}
673
+ ${d.code ?? ''}
674
+ ${(() => {
675
+ if (req.query.type === 'editor') {
676
+ return ``;
677
+ } else {
678
+ return `${(data.config.globalStyle ?? [])
679
+ .map((dd: any) => {
680
+ try {
681
+ if (dd.data.elem === 'link') {
682
+ return html` <link
683
+ type="text/css"
684
+ rel="stylesheet"
685
+ href="${dd.data.attr.find((dd: any) => {
686
+ return dd.attr === 'href';
687
+ }).value}"
688
+ />`;
689
+ }
690
+ } catch (e) {
691
+ return ``;
692
+ }
693
+ })
694
+ .join('')}`;
695
+ }
696
+ })()}
697
+ `;
698
+ })(),
699
+ `<script>
700
+ ${[
701
+ (req.query.type !== 'editor' && d.custom_script) ?? '',
702
+ `window.login_config = ${JSON.stringify(login_config)};`,
703
+ `window.appName = '${appName}';`,
704
+ `window.glitterBase = '${brandAndMemberType.brand}';`,
705
+ `window.memberType = '${brandAndMemberType.memberType}';`,
706
+ `window.glitterBackend = '${config.domain}';`,
707
+ `window.preloadData = ${JSON.stringify(preload)
708
+ .replace(/<\/script>/g, 'sdjuescript_prepand')
709
+ .replace(/<script>/g, 'sdjuescript_prefix')};`,
710
+ `window.glitter_page = '${req.query.page}';`,
711
+ `window.store_info = ${JSON.stringify(store_info)};`,
712
+ `window.server_execute_time = ${(new Date().getTime() - start) / 1000};`,
713
+ `window.language = '${language}';`,
714
+ `${distribution_code}`,
715
+ `window.ip_country = '${ip_country.country || 'TW'}';`,
716
+ `window.currency_covert = ${JSON.stringify(await Shopping.currencyCovert((req.query.base || 'TWD') as string))};`,
717
+ `window.language_list = ${JSON.stringify(language_label.label)};`,
718
+ `window.home_seo=${JSON.stringify(home_seo)
719
+ .replace(/<\/script>/g, 'sdjuescript_prepand')
720
+ .replace(/<script>/g, 'sdjuescript_prefix')};`,
721
+ ]
722
+ .map(dd => {
723
+ return (dd || '').trim();
724
+ })
725
+ .filter(dd => {
726
+ return dd;
727
+ })
728
+ .join(';\n')}
729
+ </script>
730
+ ${[
731
+ { src: 'glitterBundle/GlitterInitial.js', type: 'module' },
732
+ { src: 'glitterBundle/module/html-generate.js', type: 'module' },
733
+ { src: 'glitterBundle/html-component/widget.js', type: 'module' },
734
+ { src: 'glitterBundle/plugins/trigger-event.js', type: 'module' },
735
+ { src: 'api/pageConfig.js', type: 'module' },
736
+ ]
737
+ .map(dd => {
738
+ return html` <script
739
+ src="/${link_prefix && `${link_prefix}/`}${dd.src}"
740
+ type="${dd.type}"
741
+ ></script>`;
742
+ })
743
+ .join('')}
744
+ ${(preload.event ?? [])
745
+ .filter((dd: any) => {
746
+ return dd;
747
+ })
748
+ .map((dd: any) => {
749
+ const link = dd.fun.replace(
750
+ `TriggerEvent.setEventRouter(import.meta.url, '.`,
751
+ 'official_event'
752
+ );
753
+ return link.substring(0, link.length - 2);
754
+ })
755
+ .map(
756
+ (dd: any) =>
757
+ html` <script src="/${link_prefix && `${link_prefix}/`}${dd}" type="module"></script>`
758
+ )
759
+ .join('')}
760
+ ${(() => {
761
+ if (req.query.type === 'editor') {
762
+ return ``;
763
+ } else {
764
+ //IOS系統必須同意後才可追蹤
765
+ if((req.headers['user-agent'] as string).includes('iosGlitter') && !((req.headers['user-agent'] as string).includes('allow_track'))){
766
+ customCode.ga4=[];
767
+ customCode.g_tag=[];
768
+ }
769
+ return html`
770
+ ${SeoConfig.gA4(customCode.ga4)} ${SeoConfig.gTag(customCode.g_tag)}
771
+ ${seo_content
772
+ .map(dd => {
773
+ return dd.trim();
774
+ })
775
+ .join('\n')}
776
+ `;
777
+ }
778
+ })()}`,
779
+ ].join('');
780
+ return {
781
+ head: head,
782
+ body: ``,
783
+ seo_detail:seo
784
+ };
785
+ } else {
786
+ return {
787
+ head: await Seo.redirectToHomePage(appName, req),
788
+ body: ``,
789
+ };
790
+ }
791
+ } catch (e: any) {
792
+ console.error(e);
793
+ return {
794
+ head: ``,
795
+ body: `${e}`,
796
+ };
797
+ }
798
+ }
386
799
  }
387
800
 
388
801
  export function extractCols(data: { value: any[]; updated_at: Date }) {
@@ -437,3 +850,53 @@ function isCurrentTimeWithinRange(data: {
437
850
  return now >= startDateTime;
438
851
  }
439
852
  }
853
+
854
+
855
+ async function getSeoDetail(appName: string, req: any) {
856
+ const sqlData = await Private_config.getConfig({
857
+ appName: appName,
858
+ key: 'seo_webhook',
859
+ });
860
+ if (!sqlData[0] || !sqlData[0].value) {
861
+ return undefined;
862
+ }
863
+ const html = String.raw;
864
+ return await db.queryLambada(
865
+ {
866
+ database: appName,
867
+ },
868
+ async db => {
869
+ (db as any).execute = (db as any).query;
870
+ const functionValue: { key: string; data: () => any }[] = [
871
+ {
872
+ key: 'db',
873
+ data: () => {
874
+ return db;
875
+ },
876
+ },
877
+ {
878
+ key: 'req',
879
+ data: () => {
880
+ return req;
881
+ },
882
+ },
883
+ ];
884
+ const evalString = `
885
+ return {
886
+ execute:(${functionValue
887
+ .map(d2 => {
888
+ return d2.key;
889
+ })
890
+ .join(',')})=>{
891
+ try {
892
+ ${sqlData[0].value.value.replace(
893
+ /new\s*Promise\s*\(\s*async\s*\(\s*resolve\s*,\s*reject\s*\)\s*=>\s*\{([\s\S]*)\}\s*\)/i,
894
+ 'new Promise(async (resolve, reject) => { try { $1 } catch (error) { console.log(error);reject(error); } })'
895
+ )}
896
+ }catch (e) { console.log(e) } } }
897
+ `;
898
+ const myFunction = new Function(evalString);
899
+ return await myFunction().execute(functionValue[0].data(), functionValue[1].data());
900
+ }
901
+ );
902
+ }